use crate::{
    consts::{
        widget_piano::{
            H_ZOOM_MAX, H_ZOOM_MIN, KEYBOARD_WIDTH, MAIN_SPLIT_SPACING, MIDI_NOTE_COUNT,
            NOTES_PER_OCTAVE, OCTAVES, TOOLS_STRIP_WIDTH, WHITE_KEY_HEIGHT, WHITE_KEYS_PER_OCTAVE,
        },
        workspace::PLAYHEAD_WIDTH_PX,
    },
    menu::{menu_dropdown, menu_item},
    message::{Message, PianoControllerLane},
    state::State,
    widget::{
        controller::ControllerRollInteraction,
        piano::{self, PianoRollInteraction},
    },
};
use iced::{
    Background, Border, Color, Element, Length, Point,
    widget::{
        Id, Stack, button, checkbox, column, container, pick_list, pin, row, scrollable, slider,
        text, text_input, vertical_slider,
    },
};
use iced_aw::{
    menu::{DrawPath, Item, Menu as IcedMenu},
    menu_bar, menu_items,
};
use maolan_widgets::{
    controller::{self, ControllerKindOption},
    note_area::{
        NoteArea, PianoGridScrollCallbacks, PianoGridScrollLayout, PianoGridScrolls,
        piano_grid_scrollers,
    },
    piano::{OctaveKeyboard, row_height},
    piano_roll::PianoRoll,
    vertical_scrollbar::VerticalScrollbar,
};
use std::collections::HashMap;

#[derive(Debug)]
pub struct MIDIEdit {
    state: State,
    midnam_note_names: HashMap<u8, String>,
}

pub use crate::consts::widget_piano::{
    CTRL_SCROLL_ID, KEYS_SCROLL_ID, NOTES_SCROLL_ID, SYSEX_SCROLL_ID,
};

impl MIDIEdit {
    pub fn new(state: State) -> Self {
        Self {
            state,
            midnam_note_names: HashMap::new(),
        }
    }

    pub fn set_midnam_note_names(&mut self, names: &HashMap<u8, String>) {
        self.midnam_note_names = names.clone();
    }

    fn zoom_x_to_slider(zoom_x: f32) -> f32 {
        (H_ZOOM_MIN + H_ZOOM_MAX - zoom_x).clamp(H_ZOOM_MIN, H_ZOOM_MAX)
    }

    fn slider_to_zoom_x(slider_value: f32) -> f32 {
        (H_ZOOM_MIN + H_ZOOM_MAX - slider_value).clamp(H_ZOOM_MIN, H_ZOOM_MAX)
    }

    fn populated_menu_item(
        label: impl Into<String>,
        message: Message,
        populated: bool,
    ) -> Element<'static, Message> {
        let label = label.into();
        button(
            text(label)
                .width(Length::Fill)
                .style(move |_theme| iced::widget::text::Style {
                    color: Some(if populated {
                        Color::from_rgb(0.60, 0.88, 0.45)
                    } else {
                        Color::from_rgb(0.86, 0.86, 0.9)
                    }),
                }),
        )
        .padding([4, 8])
        .width(Length::Fill)
        .style(move |_theme, status| {
            use iced::widget::button::{Status, Style};

            let background = match status {
                Status::Hovered => {
                    if populated {
                        Color::from_rgba(0.16, 0.34, 0.16, 1.0)
                    } else {
                        Color::from_rgba(0.22, 0.23, 0.26, 1.0)
                    }
                }
                Status::Pressed => {
                    if populated {
                        Color::from_rgba(0.20, 0.42, 0.18, 1.0)
                    } else {
                        Color::from_rgba(0.30, 0.31, 0.35, 1.0)
                    }
                }
                Status::Disabled => Color::from_rgba(0.18, 0.18, 0.20, 0.7),
                Status::Active => {
                    if populated {
                        Color::from_rgba(0.12, 0.26, 0.12, 0.95)
                    } else {
                        Color::TRANSPARENT
                    }
                }
            };

            Style {
                background: Some(Background::Color(background)),
                text_color: if populated {
                    Color::from_rgb(0.60, 0.88, 0.45)
                } else {
                    Color::from_rgb(0.86, 0.86, 0.9)
                },
                border: Border::default().rounded(6.0),
                ..Style::default()
            }
        })
        .on_press(message)
        .into()
    }

    pub fn view(
        &self,
        pixels_per_sample: f32,
        samples_per_bar: f32,
        playhead_x: Option<f32>,
    ) -> Element<'_, Message> {
        let state = self.state.blocking_read();
        let zoom_x = state.piano_zoom_x;
        let zoom_y = state.piano_zoom_y;
        let humanize_time_amount = state.piano_humanize_time_amount.clamp(0.0, 1.0);
        let humanize_velocity_amount = state.piano_humanize_velocity_amount.clamp(0.0, 1.0);
        let groove_amount = state.piano_groove_amount.clamp(0.0, 1.0);
        let scale_root = state.piano_scale_root;
        let scale_minor = state.piano_scale_minor;
        let chord_kind = state.piano_chord_kind;
        let velocity_shape_amount = state.piano_velocity_shape_amount.clamp(0.0, 1.0);
        let controller_lane = state.piano_controller_lane;

        let Some(roll) = state.piano.as_ref() else {
            return container(text("No MIDI clip selected."))
                .width(Length::Fill)
                .height(Length::Fill)
                .into();
        };
        let roll = roll.clone();

        let notes_content: Element<'_, Message>;
        let notes_h: f32;
        let keyboard_scroll: Element<'_, Message>;

        let midnam_note_names = self.midnam_note_names.clone();

        let editor_view_mode = state
            .piano
            .as_ref()
            .and_then(|p| state.tracks.iter().find(|t| t.name == p.track_idx))
            .map(|t| t.midi.editor_view_mode)
            .unwrap_or_default();

        if matches!(
            editor_view_mode,
            crate::message::MidiEditorViewMode::DrumGrid
        ) {
            let mut drum_rows: Vec<u8> = roll
                .notes
                .iter()
                .map(|n| n.pitch)
                .collect::<std::collections::HashSet<_>>()
                .into_iter()
                .collect();
            for (pitch, _) in crate::consts::gm_drum_map::GM_DRUM_MAP {
                if !drum_rows.contains(pitch) {
                    drum_rows.push(*pitch);
                }
            }
            for pitch in midnam_note_names.keys() {
                if !drum_rows.contains(pitch) {
                    drum_rows.push(*pitch);
                }
            }
            drum_rows.sort();

            let drum_row_h = (24.0 * zoom_y).max(1.0);
            notes_h = drum_rows.len() as f32 * drum_row_h;
            let pps = (pixels_per_sample * zoom_x).max(0.0001);
            let notes_w = (roll.clip_length_samples as f32 * pps).max(1.0);

            let mut drum_layers: Vec<Element<'_, Message>> = vec![];

            for (idx, _pitch) in drum_rows.iter().enumerate() {
                let is_even = idx % 2 == 0;
                drum_layers.push(
                    pin(container("")
                        .width(Length::Fixed(notes_w))
                        .height(Length::Fixed(drum_row_h))
                        .style(move |_theme| container::Style {
                            background: Some(Background::Color(if is_even {
                                Color::from_rgba(0.10, 0.10, 0.12, 0.85)
                            } else {
                                Color::from_rgba(0.13, 0.13, 0.15, 0.85)
                            })),
                            ..container::Style::default()
                        }))
                    .position(Point::new(0.0, idx as f32 * drum_row_h))
                    .into(),
                );
            }

            if let Some(samples_per_bar) = Some(samples_per_bar) {
                let beat_samples = (samples_per_bar / 4.0).max(1.0);
                let mut beat = 0usize;
                loop {
                    let x = beat as f32 * beat_samples * pps;
                    if x > notes_w {
                        break;
                    }
                    let bar_line = beat.is_multiple_of(4);
                    drum_layers.push(
                        pin(container("")
                            .width(Length::Fixed(if bar_line { 2.0 } else { 1.0 }))
                            .height(Length::Fixed(notes_h))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(Color {
                                    r: if bar_line { 0.5 } else { 0.35 },
                                    g: if bar_line { 0.5 } else { 0.35 },
                                    b: if bar_line { 0.55 } else { 0.35 },
                                    a: 0.45,
                                })),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, 0.0))
                        .into(),
                    );
                    beat += 1;
                }
            }

            for note in &roll.notes {
                if let Some(row_idx) = drum_rows.iter().position(|&p| p == note.pitch) {
                    let color = maolan_widgets::piano::note_color(note.velocity, note.channel);
                    let x = note.start_sample as f32 * pps;
                    let y = row_idx as f32 * drum_row_h + (drum_row_h - 10.0) / 2.0;
                    drum_layers.push(
                        pin(container("")
                            .width(Length::Fixed(10.0))
                            .height(Length::Fixed(10.0))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(color)),
                                border: Border::default().rounded(5.0),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, y))
                        .into(),
                    );
                }
            }

            if let Some(x) = playhead_x {
                drum_layers.push(
                    pin(container("")
                        .width(Length::Fixed(PLAYHEAD_WIDTH_PX))
                        .height(Length::Fixed(notes_h))
                        .style(|_theme| container::Style {
                            background: Some(Background::Color(Color::from_rgba(
                                0.95, 0.18, 0.14, 0.95,
                            ))),
                            ..container::Style::default()
                        }))
                    .position(Point::new(x.max(0.0), 0.0))
                    .into(),
                );
            }

            let drum_interaction = maolan_widgets::drum::DrumRollInteraction::new(
                roll.notes.clone(),
                pixels_per_sample,
                zoom_x,
                drum_rows.clone(),
                drum_row_h,
                state.piano_selecting_rect,
            );
            let drum_canvas: Element<'_, maolan_widgets::drum::DrumMessage> =
                iced::widget::canvas(drum_interaction)
                    .width(Length::Fixed(notes_w))
                    .height(Length::Fixed(notes_h))
                    .into();
            let mapped_drum_canvas: Element<'_, Message> = drum_canvas.map(|msg| match msg {
                maolan_widgets::drum::DrumMessage::NoteSelected(idx) => {
                    Message::DrumNoteSelected(idx)
                }
                maolan_widgets::drum::DrumMessage::ClearSelection => {
                    Message::DrumNoteSelected(usize::MAX)
                }
                maolan_widgets::drum::DrumMessage::NoteCreate {
                    start_sample,
                    pitch,
                } => Message::DrumNoteCreate {
                    start_sample,
                    pitch,
                },
                maolan_widgets::drum::DrumMessage::NoteDelete(idx) => Message::DrumNoteDelete(idx),
                maolan_widgets::drum::DrumMessage::NoteMove {
                    note_index,
                    delta_samples,
                } => Message::DrumNoteMove {
                    note_index,
                    delta_samples,
                },
                maolan_widgets::drum::DrumMessage::AdjustVelocity { note_index, delta } => {
                    Message::PianoAdjustVelocity { note_index, delta }
                }
                maolan_widgets::drum::DrumMessage::SelectRectStart { position } => {
                    Message::DrumSelectRectStart { position }
                }
                maolan_widgets::drum::DrumMessage::SelectRectDrag { position } => {
                    Message::DrumSelectRectDrag { position }
                }
                maolan_widgets::drum::DrumMessage::SelectRectEnd => Message::DrumSelectRectEnd,
            });
            drum_layers.push(
                pin(mapped_drum_canvas)
                    .position(Point::new(0.0, 0.0))
                    .into(),
            );

            notes_content = Stack::from_vec(drum_layers)
                .width(Length::Fixed(notes_w))
                .height(Length::Fixed(notes_h))
                .into();

            let sidebar_rows = drum_rows
                .iter()
                .enumerate()
                .fold(column![], |col, (idx, pitch)| {
                    let name = midnam_note_names.get(pitch).cloned().unwrap_or_else(|| {
                        crate::consts::gm_drum_map::GM_DRUM_MAP
                            .iter()
                            .find(|(p, _)| *p == *pitch)
                            .map(|&(_, name): &(u8, &str)| name.to_string())
                            .unwrap_or_else(|| format!("Note {}", pitch))
                    });
                    col.push(
                        container(
                            row![
                                text(format!("{}", pitch))
                                    .size(9)
                                    .width(Length::Fixed(28.0)),
                                text(name).size(10).width(Length::Fill),
                            ]
                            .spacing(2),
                        )
                        .width(Length::Fill)
                        .height(Length::Fixed(drum_row_h))
                        .padding([2, 4])
                        .style(move |_theme| container::Style {
                            background: Some(Background::Color(if idx % 2 == 0 {
                                Color::from_rgba(0.10, 0.10, 0.12, 1.0)
                            } else {
                                Color::from_rgba(0.13, 0.13, 0.15, 1.0)
                            })),
                            ..container::Style::default()
                        }),
                    )
                });

            keyboard_scroll = container(sidebar_rows)
                .width(Length::Fixed(KEYBOARD_WIDTH))
                .height(Length::Fill)
                .style(|_theme| container::Style {
                    background: Some(Background::Color(Color {
                        r: 0.12,
                        g: 0.12,
                        b: 0.12,
                        a: 1.0,
                    })),
                    ..container::Style::default()
                })
                .into();
        } else {
            notes_content = NoteArea {
                zoom_x,
                zoom_y,
                pixels_per_sample,
                samples_per_bar: Some(samples_per_bar),
                playhead_x,
                playhead_width: PLAYHEAD_WIDTH_PX,
                clip_length_samples: roll.clip_length_samples,
            }
            .view(vec![
                PianoRoll::new(
                    roll.notes.clone(),
                    roll.clip_length_samples,
                    zoom_y,
                    pixels_per_sample,
                    zoom_x,
                    iced::widget::canvas(PianoRollInteraction::new(
                        self.state.clone(),
                        pixels_per_sample,
                    ))
                    .width(Length::Fixed(
                        (roll.clip_length_samples as f32
                            * (pixels_per_sample * zoom_x).max(0.0001))
                        .max(1.0),
                    ))
                    .height(Length::Fixed(MIDI_NOTE_COUNT as f32 * row_height(zoom_y)))
                    .into(),
                )
                .into_element(),
            ]);

            notes_h = MIDI_NOTE_COUNT as f32
                * ((WHITE_KEY_HEIGHT * WHITE_KEYS_PER_OCTAVE as f32 / NOTES_PER_OCTAVE as f32)
                    * zoom_y)
                    .max(1.0);

            let keyboard = (0..OCTAVES).fold(column![], |col, octave_idx| {
                let octave = (OCTAVES - 1 - octave_idx) as u8;
                let octave_h = piano::octave_note_count(octave) as f32
                    * ((WHITE_KEY_HEIGHT * WHITE_KEYS_PER_OCTAVE as f32 / NOTES_PER_OCTAVE as f32)
                        * zoom_y)
                        .max(1.0);
                let names = midnam_note_names.clone();
                col.push(
                    container(
                        iced::widget::canvas(OctaveKeyboard::new(
                            octave,
                            names,
                            Message::PianoKeyPressed,
                            Message::PianoKeyReleased,
                        ))
                        .width(Length::Fixed(KEYBOARD_WIDTH))
                        .height(Length::Fixed(octave_h)),
                    )
                    .id(Id::from(format!("kb_{octave}_{}", midnam_note_names.len())))
                    .padding(0),
                )
            });
            keyboard_scroll = container(
                keyboard
                    .width(Length::Fixed(KEYBOARD_WIDTH))
                    .height(Length::Fill),
            )
            .width(Length::Fixed(KEYBOARD_WIDTH))
            .height(Length::Fill)
            .style(|_theme| container::Style {
                background: Some(Background::Color(Color {
                    r: 0.12,
                    g: 0.12,
                    b: 0.12,
                    a: 1.0,
                })),
                ..container::Style::default()
            })
            .into();
        }

        let ctrl_line_count = controller::controller_lane_line_count(controller_lane).max(1);
        let ctrl_h = (ctrl_line_count as f32).max(140.0);
        let ctrl_row_h = (ctrl_h / ctrl_line_count as f32).max(1.0);
        let pps_ctrl = (pixels_per_sample * zoom_x).max(0.0001);
        let ctrl_w = (roll.clip_length_samples as f32 * pps_ctrl).max(1.0);

        let mut ctrl_layers: Vec<Element<'_, Message>> = vec![
            pin(container("")
                .width(Length::Fixed(ctrl_w))
                .height(Length::Fixed(ctrl_h))
                .style(|_theme| container::Style {
                    background: Some(Background::Color(Color {
                        r: 0.16,
                        g: 0.16,
                        b: 0.18,
                        a: 0.9,
                    })),
                    ..container::Style::default()
                }))
            .position(Point::new(0.0, 0.0))
            .into(),
        ];

        for row in 0..ctrl_line_count {
            let y = row as f32 * ctrl_row_h;
            let divider = if (row % 8) == 0 { 0.28 } else { 0.2 };
            ctrl_layers.push(
                pin(container("")
                    .width(Length::Fixed(ctrl_w))
                    .height(Length::Fixed(1.0))
                    .style(move |_theme| container::Style {
                        background: Some(Background::Color(Color {
                            r: divider,
                            g: divider,
                            b: divider + 0.02,
                            a: 0.5,
                        })),
                        ..container::Style::default()
                    }))
                .position(Point::new(0.0, y))
                .into(),
            );
        }

        let beat_samples = (samples_per_bar / 4.0).max(1.0);
        let mut beat = 0usize;
        loop {
            let x_ctrl = beat as f32 * beat_samples * pps_ctrl;
            if x_ctrl > ctrl_w {
                break;
            }
            let bar_line = beat.is_multiple_of(4);
            if x_ctrl <= ctrl_w {
                ctrl_layers.push(
                    pin(container("")
                        .width(Length::Fixed(if bar_line { 2.0 } else { 1.0 }))
                        .height(Length::Fixed(ctrl_h))
                        .style(move |_theme| container::Style {
                            background: Some(Background::Color(Color {
                                r: if bar_line { 0.5 } else { 0.35 },
                                g: if bar_line { 0.5 } else { 0.35 },
                                b: if bar_line { 0.55 } else { 0.35 },
                                a: 0.45,
                            })),
                            ..container::Style::default()
                        }))
                    .position(Point::new(x_ctrl, 0.0))
                    .into(),
                );
            }
            beat += 1;
        }

        match controller_lane {
            PianoControllerLane::Controller => {
                for (idx, row) in
                    controller::lane_controller_events(controller_lane, &roll.controllers)
                {
                    let ctrl = &roll.controllers[idx];
                    let x = ctrl.sample as f32 * pps_ctrl;
                    let mut color = controller::controller_color(ctrl.controller, ctrl.channel);
                    color.a = (0.2 + (ctrl.value as f32 / 127.0) * 0.8).clamp(0.2, 1.0);
                    let stem_h = (ctrl_h * (ctrl.value as f32 / 127.0)).max(1.0);
                    let stem_y = ctrl_h - stem_h;
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(2.0))
                            .height(Length::Fixed(stem_h))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(color)),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, stem_y))
                        .into(),
                    );
                    let y = row as f32 * ctrl_row_h;
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(2.0))
                            .height(Length::Fixed(1.0))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(Color::from_rgba(
                                    1.0, 1.0, 1.0, 0.35,
                                ))),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, y))
                        .into(),
                    );
                }
            }
            PianoControllerLane::Velocity => {
                for note in &roll.notes {
                    let x = note.start_sample as f32 * pps_ctrl;
                    let row = usize::from(127_u8.saturating_sub(note.velocity));
                    let y = row as f32 * ctrl_row_h;
                    let mut color = piano::note_color(note.velocity, note.channel);
                    color.a = 0.9;
                    let stem_h = (ctrl_h - y).max(ctrl_row_h);
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(2.0))
                            .height(Length::Fixed(stem_h))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(color)),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, y))
                        .into(),
                    );
                }
            }
            PianoControllerLane::Rpn => {
                for (idx, row) in
                    controller::lane_controller_events(controller_lane, &roll.controllers)
                {
                    let ctrl = &roll.controllers[idx];
                    let x = ctrl.sample as f32 * pps_ctrl;
                    let mut color = controller::controller_color(ctrl.controller, ctrl.channel);
                    color.a = (0.2 + (ctrl.value as f32 / 127.0) * 0.8).clamp(0.2, 1.0);
                    let stem_h = (ctrl_h * (ctrl.value as f32 / 127.0)).max(1.0);
                    let stem_y = ctrl_h - stem_h;
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(2.0))
                            .height(Length::Fixed(stem_h))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(color)),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, stem_y))
                        .into(),
                    );
                    let y = row as f32 * ctrl_row_h;
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(2.0))
                            .height(Length::Fixed(1.0))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(Color::from_rgba(
                                    1.0, 1.0, 1.0, 0.35,
                                ))),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, y))
                        .into(),
                    );
                }
            }
            PianoControllerLane::Nrpn => {
                for (idx, row) in
                    controller::lane_controller_events(controller_lane, &roll.controllers)
                {
                    let ctrl = &roll.controllers[idx];
                    let x = ctrl.sample as f32 * pps_ctrl;
                    let mut color = controller::controller_color(ctrl.controller, ctrl.channel);
                    color.a = (0.2 + (ctrl.value as f32 / 127.0) * 0.8).clamp(0.2, 1.0);
                    let stem_h = (ctrl_h * (ctrl.value as f32 / 127.0)).max(1.0);
                    let stem_y = ctrl_h - stem_h;
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(2.0))
                            .height(Length::Fixed(stem_h))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(color)),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, stem_y))
                        .into(),
                    );
                    let y = row as f32 * ctrl_row_h;
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(2.0))
                            .height(Length::Fixed(1.0))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(Color::from_rgba(
                                    1.0, 1.0, 1.0, 0.35,
                                ))),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, y))
                        .into(),
                    );
                }
            }
            PianoControllerLane::SysEx => {
                for (idx, sysex) in roll.sysexes.iter().enumerate() {
                    let x = sysex.sample as f32 * pps_ctrl;
                    let selected = state.piano_selected_sysex == Some(idx);
                    let color = if selected {
                        Color::from_rgba(1.0, 0.55, 0.2, 0.95)
                    } else {
                        Color::from_rgba(0.95, 0.35, 0.2, 0.75)
                    };
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(2.0))
                            .height(Length::Fixed(ctrl_h))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(color)),
                                ..container::Style::default()
                            }))
                        .position(Point::new(x, 0.0))
                        .into(),
                    );
                    ctrl_layers.push(
                        pin(container("")
                            .width(Length::Fixed(6.0))
                            .height(Length::Fixed(6.0))
                            .style(move |_theme| container::Style {
                                background: Some(Background::Color(color)),
                                ..container::Style::default()
                            }))
                        .position(Point::new((x - 2.0).max(0.0), 0.0))
                        .into(),
                    );
                }
            }
        }

        if let Some(x) = playhead_x {
            let x = x.max(0.0);
            ctrl_layers.push(
                pin(container("")
                    .width(Length::Fixed(PLAYHEAD_WIDTH_PX))
                    .height(Length::Fixed(ctrl_h))
                    .style(|_theme| container::Style {
                        background: Some(Background::Color(Color {
                            r: 0.95,
                            g: 0.18,
                            b: 0.14,
                            a: 0.95,
                        })),
                        ..container::Style::default()
                    }))
                .position(Point::new(x, 0.0))
                .into(),
            );
        }

        ctrl_layers.push(
            pin(iced::widget::canvas(ControllerRollInteraction::new(
                self.state.clone(),
                pixels_per_sample,
                (samples_per_bar as f64 * state.tempo as f64 / 240.0).max(1.0),
                samples_per_bar,
            ))
            .width(Length::Fixed(ctrl_w))
            .height(Length::Fixed(ctrl_h)))
            .position(Point::new(0.0, 0.0))
            .into(),
        );

        let ctrl_content = Stack::from_vec(ctrl_layers)
            .width(Length::Fixed(ctrl_w))
            .height(Length::Fixed(ctrl_h));

        let populated_ccs = controller::populated_controller_ccs(&roll.controllers);
        let populated_rpn_rows =
            controller::populated_controller_rows(PianoControllerLane::Rpn, &roll.controllers);
        let populated_nrpn_rows =
            controller::populated_controller_rows(PianoControllerLane::Nrpn, &roll.controllers);
        let controller_picker = pick_list(
            vec![
                PianoControllerLane::Controller,
                PianoControllerLane::Velocity,
                PianoControllerLane::Rpn,
                PianoControllerLane::Nrpn,
                PianoControllerLane::SysEx,
            ],
            Some(state.piano_controller_lane),
            Message::PianoControllerLaneSelected,
        )
        .width(Length::Fill);
        let controller_number_picker: Element<'_, Message> = match state.piano_controller_lane {
            PianoControllerLane::Controller => {
                let selected = format!("CC{:03} \u{25BE}", state.piano_controller_kind);
                let cc_menu = IcedMenu::new(
                    (0u8..=127)
                        .map(|cc| {
                            Item::new(Self::populated_menu_item(
                                ControllerKindOption(cc).to_string(),
                                Message::PianoControllerKindSelected(cc),
                                populated_ccs.contains(&cc),
                            ))
                        })
                        .collect::<Vec<_>>(),
                )
                .width(320.0)
                .offset(10.0)
                .spacing(4.0);
                #[rustfmt::skip]
                let picker = menu_bar!(
                    (menu_dropdown(selected, Message::None), {
                        cc_menu
                    })
                )
                .draw_path(DrawPath::Backdrop)
                .close_on_item_click_global(true)
                .width(Length::Fill);
                picker.into()
            }
            PianoControllerLane::Velocity => {
                let selected = format!("{} \u{25BE}", state.piano_velocity_kind);
                let velocity_menu = IcedMenu::new(
                    crate::consts::message_lists::PIANO_VELOCITY_KIND_ALL
                        .iter()
                        .copied()
                        .map(|kind| {
                            Item::new(menu_item(
                                kind.to_string(),
                                Message::PianoVelocityKindSelected(kind),
                            ))
                        })
                        .collect::<Vec<_>>(),
                )
                .width(280.0)
                .offset(10.0)
                .spacing(4.0);
                #[rustfmt::skip]
                let picker = menu_bar!(
                    (menu_dropdown(selected, Message::None), {
                        velocity_menu
                    })
                )
                .draw_path(DrawPath::Backdrop)
                .close_on_item_click_global(true)
                .width(Length::Fill);
                picker.into()
            }
            PianoControllerLane::Rpn => {
                let selected = format!("{} \u{25BE}", state.piano_rpn_kind);
                let rpn_menu = IcedMenu::new(
                    crate::consts::message_lists::PIANO_RPN_KIND_ALL
                        .iter()
                        .copied()
                        .enumerate()
                        .map(|(row, kind)| {
                            Item::new(Self::populated_menu_item(
                                kind.to_string(),
                                Message::PianoRpnKindSelected(kind),
                                populated_rpn_rows.contains(&row),
                            ))
                        })
                        .collect::<Vec<_>>(),
                )
                .width(300.0)
                .offset(10.0)
                .spacing(4.0);
                #[rustfmt::skip]
                let picker = menu_bar!(
                    (menu_dropdown(selected, Message::None), {
                        rpn_menu
                    })
                )
                .draw_path(DrawPath::Backdrop)
                .close_on_item_click_global(true)
                .width(Length::Fill);
                picker.into()
            }
            PianoControllerLane::Nrpn => {
                let selected = format!("{} \u{25BE}", state.piano_nrpn_kind);
                let nrpn_menu = IcedMenu::new(
                    crate::consts::message_lists::PIANO_NRPN_KIND_ALL
                        .iter()
                        .copied()
                        .enumerate()
                        .map(|(row, kind)| {
                            Item::new(Self::populated_menu_item(
                                kind.to_string(),
                                Message::PianoNrpnKindSelected(kind),
                                populated_nrpn_rows.contains(&row),
                            ))
                        })
                        .collect::<Vec<_>>(),
                )
                .width(300.0)
                .offset(10.0)
                .spacing(4.0);
                #[rustfmt::skip]
                let picker = menu_bar!(
                    (menu_dropdown(selected, Message::None), {
                        nrpn_menu
                    })
                )
                .draw_path(DrawPath::Backdrop)
                .close_on_item_click_global(true)
                .width(Length::Fill);
                picker.into()
            }
            PianoControllerLane::SysEx => text("SysEx events")
                .size(10)
                .style(|_theme| iced::widget::text::Style {
                    color: Some(Color::from_rgb(0.86, 0.86, 0.9)),
                })
                .into(),
        };
        let controller_header = column![controller_picker, controller_number_picker].spacing(2);
        let controller_key = container(controller_header)
            .width(Length::Fixed(KEYBOARD_WIDTH))
            .height(Length::Fixed(ctrl_h))
            .padding([4, 3])
            .style(|_theme| container::Style {
                background: Some(Background::Color(Color {
                    r: 0.15,
                    g: 0.15,
                    b: 0.16,
                    a: 1.0,
                })),
                ..container::Style::default()
            });

        let PianoGridScrolls {
            keyboard_scroll,
            note_scroll,
            h_scroll,
            v_scroll,
        } = piano_grid_scrollers(
            keyboard_scroll,
            notes_content,
            PianoGridScrollLayout {
                notes_h,
                notes_w: ctrl_w,
                scroll_x: state.piano_scroll_x,
                scroll_y: state.piano_scroll_y,
            },
            PianoGridScrollCallbacks {
                on_scroll_y: Message::PianoScrollYChanged,
                on_scroll_xy: |x, y| Message::PianoScrollChanged { x, y },
            },
        );

        let ctrl_scroll = scrollable(
            container(ctrl_content)
                .width(Length::Shrink)
                .height(Length::Fixed(ctrl_h))
                .style(|_theme| container::Style {
                    background: Some(Background::Color(Color {
                        r: 0.12,
                        g: 0.12,
                        b: 0.13,
                        a: 1.0,
                    })),
                    ..container::Style::default()
                }),
        )
        .id(Id::new(CTRL_SCROLL_ID))
        .direction(scrollable::Direction::Horizontal(
            scrollable::Scrollbar::hidden(),
        ))
        .on_scroll(|viewport| Message::PianoScrollXChanged(viewport.relative_offset().x))
        .width(Length::Fill)
        .height(Length::Fixed(ctrl_h));

        let selected_sysex = state.piano_selected_sysex;
        let sysex_rows = roll
            .sysexes
            .iter()
            .enumerate()
            .fold(column![], |col, (idx, ev)| {
                let is_selected = selected_sysex == Some(idx);
                let label = format!("{:>8}  {}", ev.sample, controller::sysex_preview(&ev.data));
                col.push(
                    button(text(label).size(11))
                        .on_press(Message::PianoSysExSelect(Some(idx)))
                        .style(move |_theme, _status| iced::widget::button::Style {
                            background: Some(Background::Color(if is_selected {
                                Color::from_rgba(0.38, 0.28, 0.18, 1.0)
                            } else {
                                Color::from_rgba(0.17, 0.17, 0.19, 1.0)
                            })),
                            text_color: if is_selected {
                                Color::from_rgb(1.0, 0.86, 0.6)
                            } else {
                                Color::from_rgb(0.82, 0.82, 0.86)
                            },
                            ..Default::default()
                        })
                        .width(Length::Fill),
                )
            });
        let sysex_content_height = ((roll.sysexes.len() as f32) * 24.0).max(1.0);
        let sysex_panel = container(
            column![
                text("SysEx").size(12),
                text_input("F0 ... F7", &state.piano_sysex_hex_input)
                    .on_input(Message::PianoSysExHexInput)
                    .size(12)
                    .padding(6),
                row![
                    button(text("Add").size(11)).on_press(Message::PianoSysExAdd),
                    button(text("Update").size(11)).on_press(Message::PianoSysExUpdate),
                    button(text("Delete").size(11)).on_press(Message::PianoSysExDelete),
                    button(text("Cancel").size(11)).on_press(Message::PianoSysExCloseEditor),
                ]
                .spacing(4),
                row![
                    scrollable(sysex_rows.spacing(2).width(Length::Fill))
                        .id(Id::new(SYSEX_SCROLL_ID))
                        .height(Length::Fill)
                        .direction(scrollable::Direction::Vertical(
                            scrollable::Scrollbar::hidden(),
                        ))
                        .on_scroll(|viewport| {
                            Message::PianoSysExScrollYChanged(viewport.relative_offset().y)
                        }),
                    VerticalScrollbar::new(
                        sysex_content_height,
                        state.piano_sysex_scroll_y,
                        Message::PianoSysExScrollYChanged,
                    )
                    .width(Length::Fixed(16.0))
                    .height(Length::Fill),
                ]
                .spacing(0)
                .height(Length::Fill),
            ]
            .spacing(6)
            .height(Length::Fill),
        )
        .width(Length::Fixed(280.0))
        .height(Length::Fill)
        .padding([6, 6])
        .style(|_theme| container::Style {
            background: Some(Background::Color(Color::from_rgba(0.11, 0.11, 0.13, 1.0))),
            ..container::Style::default()
        });
        let edit_tools_strip = container(
            column![
                text("MIDI Tools").size(12),
                pick_list(
                    vec![
                        crate::message::MidiEditorViewMode::PianoRoll,
                        crate::message::MidiEditorViewMode::DrumGrid,
                    ],
                    Some(editor_view_mode),
                    Message::MidiEditorViewModeSelected,
                )
                .width(Length::Fill),
                row![
                    button(text("Scale").size(11)).on_press(Message::PianoScaleSelectedNotes),
                    pick_list(
                        crate::consts::message_lists::PIANO_SCALE_ROOT_ALL.to_vec(),
                        Some(scale_root),
                        Message::PianoScaleRootSelected
                    )
                    .width(Length::Fixed(62.0)),
                    checkbox(scale_minor)
                        .label("Min")
                        .on_toggle(Message::PianoScaleMinorToggled),
                ]
                .spacing(6),
                row![
                    button(text("Chord").size(11)).on_press(Message::PianoChordSelectedNotes),
                    pick_list(
                        crate::consts::message_lists::PIANO_CHORD_KIND_ALL.to_vec(),
                        Some(chord_kind),
                        Message::PianoChordKindSelected
                    )
                    .width(Length::Fixed(86.0)),
                ]
                .spacing(6),
                button(text("Legato").size(11)).on_press(Message::PianoLegatoSelectedNotes),
                row![
                    button(text("VelShape").size(11))
                        .on_press(Message::PianoVelocityShapeSelectedNotes),
                    slider(
                        0.0..=1.0,
                        velocity_shape_amount,
                        Message::PianoVelocityShapeAmountChanged
                    )
                    .step(0.01)
                    .width(Length::Fill),
                ]
                .spacing(6),
                button(text("Quantize").size(11)).on_press(Message::PianoQuantizeSelectedNotes),
                row![
                    button(text("Humanize").size(11)).on_press(Message::PianoHumanizeSelectedNotes),
                    text("T").size(10),
                    slider(
                        0.0..=1.0,
                        humanize_time_amount,
                        Message::PianoHumanizeTimeAmountChanged,
                    )
                    .step(0.01)
                    .width(Length::Fill),
                    text("V").size(10),
                    slider(
                        0.0..=1.0,
                        humanize_velocity_amount,
                        Message::PianoHumanizeVelocityAmountChanged,
                    )
                    .step(0.01)
                    .width(Length::Fill),
                ]
                .spacing(6),
                row![
                    button(text("Groove").size(11)).on_press(Message::PianoGrooveSelectedNotes),
                    slider(0.0..=1.0, groove_amount, Message::PianoGrooveAmountChanged)
                        .step(0.01)
                        .width(Length::Fill),
                ]
                .spacing(6),
            ]
            .spacing(8)
            .width(Length::Fill),
        )
        .width(Length::Fixed(TOOLS_STRIP_WIDTH))
        .height(Length::Fill)
        .padding([8, 8])
        .style(|_theme| container::Style {
            background: Some(Background::Color(Color::from_rgba(0.10, 0.10, 0.12, 1.0))),
            ..container::Style::default()
        });

        let mut layout = row![
            row![
                edit_tools_strip,
                column![
                    row![keyboard_scroll, note_scroll]
                        .height(Length::Fill)
                        .width(Length::Fill),
                    row![controller_key, ctrl_scroll],
                    row![
                        container("")
                            .width(Length::Fixed(KEYBOARD_WIDTH))
                            .height(Length::Fixed(16.0)),
                        row![
                            h_scroll,
                            slider(
                                H_ZOOM_MIN..=H_ZOOM_MAX,
                                Self::zoom_x_to_slider(zoom_x),
                                |value| Message::PianoZoomXChanged(Self::slider_to_zoom_x(value)),
                            )
                            .step(0.1)
                            .width(Length::Fixed(100.0)),
                        ]
                        .spacing(8)
                        .width(Length::Fill),
                    ]
                ]
                .spacing(3)
                .width(Length::Fill)
                .height(Length::Fill),
            ]
            .spacing(MAIN_SPLIT_SPACING)
            .width(Length::Fill)
            .height(Length::Fill),
            column![
                v_scroll,
                vertical_slider(1.0..=8.0, zoom_y, Message::PianoZoomYChanged)
                    .step(0.1)
                    .height(Length::Fixed(100.0)),
            ]
            .spacing(8)
            .height(Length::Fill),
        ];
        if state.piano_sysex_panel_open
            && matches!(state.piano_controller_lane, PianoControllerLane::SysEx)
        {
            layout = layout.push(sysex_panel);
        }
        container(layout)
            .width(Length::Fill)
            .height(Length::Fill)
            .into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;
    use tokio::sync::RwLock;

    #[test]
    fn zoom_x_to_slider_roundtrip() {
        let original = 1.0;
        let slider = MIDIEdit::zoom_x_to_slider(original);
        let back = MIDIEdit::slider_to_zoom_x(slider);
        assert!((back - original).abs() < 0.001, "Roundtrip failed");
    }

    #[test]
    fn zoom_x_to_slider_clamps_min() {
        let result = MIDIEdit::zoom_x_to_slider(0.1);
        assert!(result >= H_ZOOM_MIN);
    }

    #[test]
    fn zoom_x_to_slider_clamps_max() {
        let result = MIDIEdit::zoom_x_to_slider(100.0);
        assert!(result <= H_ZOOM_MAX);
    }

    #[test]
    fn midi_edit_new_creates_instance() {
        let state = Arc::new(RwLock::new(crate::state::StateData::default()));
        let _midi_edit = MIDIEdit::new(state);
    }
}
