/// Script Editor pane - unified code editor for WGSL shaders and BeamDSP scripts
///
/// Supports multiple editor modes:
/// - Shader: WGSL shader code for custom visual effects
/// - BeamDSP: Audio DSP scripts for scriptable audio nodes
///
/// Both modes use the same save/load workflow through the asset library.

use eframe::egui::{self, Ui};
use egui_code_editor::{CodeEditor, ColorTheme, Syntax};
use lightningbeam_core::effect::{EffectCategory, EffectDefinition};
use lightningbeam_core::script::ScriptDefinition;
use uuid::Uuid;
use super::{NodePath, PaneRenderer, SharedPaneState};

/// Editor mode determines syntax, templates, and compile/save behavior
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EditorMode {
    /// WGSL shader for visual effects
    Shader,
    /// BeamDSP script for audio processing nodes
    BeamDSP,
}

/// Result from the unsaved changes dialog
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum UnsavedDialogResult {
    Cancel,
    Discard,
    SaveAndContinue,
}

// ── Syntax definitions ──────────────────────────────────────────────

fn wgsl_syntax() -> Syntax {
    Syntax {
        language: "WGSL",
        case_sensitive: true,
        comment: "//",
        comment_multiline: ["/*", "*/"],
        hyperlinks: std::collections::BTreeSet::new(),
        keywords: std::collections::BTreeSet::from([
            "if", "else", "for", "while", "loop", "break", "continue", "return",
            "switch", "case", "default", "discard",
            "fn", "let", "var", "const", "struct", "alias", "type",
            "function", "private", "workgroup", "uniform", "storage",
            "read", "write", "read_write",
            "vertex", "fragment", "compute",
            "location", "builtin", "group", "binding",
            "position", "vertex_index", "instance_index", "front_facing",
            "frag_depth", "local_invocation_id", "local_invocation_index",
            "global_invocation_id", "workgroup_id", "num_workgroups",
            "sample_index", "sample_mask",
        ]),
        types: std::collections::BTreeSet::from([
            "bool", "i32", "u32", "f32", "f16",
            "vec2", "vec3", "vec4",
            "vec2i", "vec3i", "vec4i", "vec2u", "vec3u", "vec4u",
            "vec2f", "vec3f", "vec4f", "vec2h", "vec3h", "vec4h",
            "mat2x2", "mat2x3", "mat2x4", "mat3x2", "mat3x3", "mat3x4",
            "mat4x2", "mat4x3", "mat4x4", "mat2x2f", "mat3x3f", "mat4x4f",
            "texture_1d", "texture_2d", "texture_2d_array", "texture_3d",
            "texture_cube", "texture_cube_array", "texture_multisampled_2d",
            "texture_storage_1d", "texture_storage_2d", "texture_storage_2d_array",
            "texture_storage_3d", "texture_depth_2d", "texture_depth_2d_array",
            "texture_depth_cube", "texture_depth_cube_array",
            "texture_depth_multisampled_2d",
            "sampler", "sampler_comparison",
            "array", "ptr",
        ]),
        special: std::collections::BTreeSet::from([
            "abs", "acos", "all", "any", "asin", "atan", "atan2",
            "ceil", "clamp", "cos", "cosh", "cross",
            "degrees", "determinant", "distance", "dot",
            "exp", "exp2", "faceForward", "floor", "fma", "fract",
            "length", "log", "log2",
            "max", "min", "mix", "modf", "normalize",
            "pow", "radians", "reflect", "refract", "round",
            "saturate", "sign", "sin", "sinh", "smoothstep", "sqrt", "step",
            "tan", "tanh", "transpose", "trunc",
            "textureSample", "textureSampleLevel", "textureSampleBias",
            "textureSampleGrad", "textureSampleCompare", "textureLoad",
            "textureStore", "textureDimensions", "textureNumLayers",
            "textureNumLevels", "textureNumSamples",
            "atomicLoad", "atomicStore", "atomicAdd", "atomicSub",
            "atomicMax", "atomicMin", "atomicAnd", "atomicOr", "atomicXor",
            "atomicExchange", "atomicCompareExchangeWeak",
            "pack4x8snorm", "pack4x8unorm", "pack2x16snorm", "pack2x16unorm",
            "unpack4x8snorm", "unpack4x8unorm", "unpack2x16snorm", "unpack2x16unorm",
            "storageBarrier", "workgroupBarrier", "workgroupUniformLoad",
            "select", "bitcast",
        ]),
    }
}

fn beamdsp_syntax() -> Syntax {
    Syntax {
        language: "BeamDSP",
        case_sensitive: true,
        comment: "//",
        comment_multiline: ["/*", "*/"],
        hyperlinks: std::collections::BTreeSet::new(),
        keywords: std::collections::BTreeSet::from([
            "name", "category", "inputs", "outputs", "params", "state", "ui", "process",
            "if", "else", "for", "in", "let", "mut",
            "generator", "effect", "utility",
            "audio", "cv", "midi",
            "param", "sample", "group", "canvas", "spacer", "draw",
        ]),
        types: std::collections::BTreeSet::from([
            "f32", "int", "bool",
        ]),
        special: std::collections::BTreeSet::from([
            "sin", "cos", "tan", "asin", "acos", "atan", "atan2",
            "exp", "log", "log2", "pow", "sqrt",
            "floor", "ceil", "round", "trunc", "fract",
            "abs", "clamp", "min", "max", "sign",
            "mix", "smoothstep",
            "len", "cv_or", "float",
            "sample_len", "sample_read", "sample_rate_of",
            "sample_rate", "buffer_size",
            "fill_circle", "stroke_circle", "stroke_arc",
            "fill_rect", "stroke_rect", "line",
            "mouse_x", "mouse_y", "mouse_down",
        ]),
    }
}

// ── Templates ───────────────────────────────────────────────────────

const DEFAULT_SHADER_TEMPLATE: &str = r#"// Custom Effect Shader
// Input: source_tex (the layer content)
// Output: vec4<f32> color at each pixel

@group(0) @binding(0) var source_tex: texture_2d<f32>;
@group(0) @binding(1) var source_sampler: sampler;

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
}

@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
    var out: VertexOutput;
    let x = f32((vertex_index & 1u) << 1u);
    let y = f32(vertex_index & 2u);
    out.position = vec4<f32>(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);
    out.uv = vec2<f32>(x, y);
    return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let color = textureSample(source_tex, source_sampler, in.uv);
    return color;
}
"#;

const GRAYSCALE_TEMPLATE: &str = r#"// Grayscale Effect
@group(0) @binding(0) var source_tex: texture_2d<f32>;
@group(0) @binding(1) var source_sampler: sampler;

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
}

@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
    var out: VertexOutput;
    let x = f32((vertex_index & 1u) << 1u);
    let y = f32(vertex_index & 2u);
    out.position = vec4<f32>(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);
    out.uv = vec2<f32>(x, y);
    return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let color = textureSample(source_tex, source_sampler, in.uv);
    let luminance = dot(color.rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
    return vec4<f32>(luminance, luminance, luminance, color.a);
}
"#;

const VIGNETTE_TEMPLATE: &str = r#"// Vignette Effect
@group(0) @binding(0) var source_tex: texture_2d<f32>;
@group(0) @binding(1) var source_sampler: sampler;

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
}

@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
    var out: VertexOutput;
    let x = f32((vertex_index & 1u) << 1u);
    let y = f32(vertex_index & 2u);
    out.position = vec4<f32>(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);
    out.uv = vec2<f32>(x, y);
    return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let color = textureSample(source_tex, source_sampler, in.uv);
    let center = vec2<f32>(0.5, 0.5);
    let dist = distance(in.uv, center);
    let radius = 0.7;
    let softness = 0.4;
    let vignette = smoothstep(radius + softness, radius, dist);
    return vec4<f32>(color.rgb * vignette, color.a);
}
"#;

const BEAMDSP_PASSTHROUGH: &str = r#"name "Passthrough"
category effect

inputs {
    audio_in: audio
}

outputs {
    audio_out: audio
}

process {
    for i in 0..buffer_size {
        audio_out[i * 2] = audio_in[i * 2];
        audio_out[i * 2 + 1] = audio_in[i * 2 + 1];
    }
}
"#;

const BEAMDSP_GAIN: &str = r#"name "Simple Gain"
category effect

inputs {
    audio_in: audio
}

outputs {
    audio_out: audio
}

params {
    gain: 1.0 [0.0, 2.0] ""
}

ui {
    param gain
}

process {
    for i in 0..buffer_size {
        audio_out[i * 2] = audio_in[i * 2] * gain;
        audio_out[i * 2 + 1] = audio_in[i * 2 + 1] * gain;
    }
}
"#;

const BEAMDSP_STEREO_DELAY: &str = r#"name "Stereo Delay"
category effect

inputs {
    audio_in: audio
}

outputs {
    audio_out: audio
}

params {
    delay_time: 0.5 [0.01, 2.0] "s"
    feedback:   0.3 [0.0, 0.95] ""
    mix:        0.5 [0.0, 1.0]  ""
}

state {
    buffer: [88200]f32
    write_pos: int
}

ui {
    param delay_time
    param feedback
    param mix
}

process {
    let delay_samples = int(delay_time * float(sample_rate)) * 2;
    for i in 0..buffer_size {
        let l = audio_in[i * 2];
        let r = audio_in[i * 2 + 1];
        let read_pos = (write_pos - delay_samples + len(buffer)) % len(buffer);
        let dl = buffer[read_pos];
        let dr = buffer[read_pos + 1];
        buffer[write_pos] = l + dl * feedback;
        buffer[write_pos + 1] = r + dr * feedback;
        write_pos = (write_pos + 2) % len(buffer);
        audio_out[i * 2]     = l * (1.0 - mix) + dl * mix;
        audio_out[i * 2 + 1] = r * (1.0 - mix) + dr * mix;
    }
}
"#;

const BEAMDSP_LFO: &str = r#"name "Custom LFO"
category generator

outputs {
    cv_out: cv
}

params {
    rate: 1.0 [0.01, 20.0] "Hz"
    depth: 1.0 [0.0, 1.0] ""
}

state {
    phase: f32
}

ui {
    param rate
    param depth
}

process {
    let inc = rate / float(sample_rate);
    for i in 0..buffer_size {
        cv_out[i] = sin(phase * 6.2831853) * depth;
        phase = phase + inc;
        if phase >= 1.0 {
            phase = phase - 1.0;
        }
    }
}
"#;

// ── Pane state ──────────────────────────────────────────────────────

/// Script Editor pane state — unified editor for shaders and DSP scripts
pub struct ShaderEditorPane {
    /// Current editor mode
    mode: EditorMode,
    /// The source code being edited
    code: String,
    /// Display name for the asset being edited
    asset_name: String,
    /// Error message from last compilation attempt
    compile_error: Option<String>,

    // ── Shader mode state ───────────────────────────────────
    /// ID of effect being edited (None = new effect)
    editing_effect_id: Option<Uuid>,

    // ── BeamDSP mode state ──────────────────────────────────
    /// ID of script being edited (None = new script)
    editing_script_id: Option<Uuid>,

    // ── Shared state ────────────────────────────────────────
    /// Original code when asset was loaded (for dirty checking)
    original_code: Option<String>,
    /// Original name when asset was loaded (for dirty checking)
    original_name: Option<String>,
    /// Effect awaiting confirmation to load (when there are unsaved changes)
    pending_load_effect: Option<EffectDefinition>,
    /// Script awaiting confirmation to load (when there are unsaved changes)
    pending_load_script: Option<ScriptDefinition>,
    /// Whether to show the unsaved changes confirmation dialog
    show_unsaved_dialog: bool,
}

impl ShaderEditorPane {
    pub fn new() -> Self {
        Self {
            mode: EditorMode::Shader,
            code: DEFAULT_SHADER_TEMPLATE.to_string(),
            asset_name: "Custom Effect".to_string(),
            compile_error: None,
            editing_effect_id: None,
            editing_script_id: None,
            original_code: None,
            original_name: None,
            pending_load_effect: None,
            pending_load_script: None,
            show_unsaved_dialog: false,
        }
    }

    fn default_code(&self) -> &str {
        match self.mode {
            EditorMode::Shader => DEFAULT_SHADER_TEMPLATE,
            EditorMode::BeamDSP => BEAMDSP_PASSTHROUGH,
        }
    }

    fn default_name(&self) -> &str {
        match self.mode {
            EditorMode::Shader => "Custom Effect",
            EditorMode::BeamDSP => "New Script",
        }
    }

    fn has_unsaved_changes(&self) -> bool {
        match (&self.original_code, &self.original_name) {
            (Some(orig_code), Some(orig_name)) => {
                self.code != *orig_code || self.asset_name != *orig_name
            }
            (None, None) => {
                self.code != self.default_code() || self.asset_name != self.default_name()
            }
            _ => true,
        }
    }

    fn is_editing_existing(&self) -> bool {
        match self.mode {
            EditorMode::Shader => self.editing_effect_id.is_some(),
            EditorMode::BeamDSP => self.editing_script_id.is_some(),
        }
    }

    fn mark_saved_state(&mut self) {
        self.original_code = Some(self.code.clone());
        self.original_name = Some(self.asset_name.clone());
    }

    fn new_asset(&mut self) {
        self.asset_name = self.default_name().to_string();
        self.code = self.default_code().to_string();
        match self.mode {
            EditorMode::Shader => self.editing_effect_id = None,
            EditorMode::BeamDSP => self.editing_script_id = None,
        }
        self.original_code = None;
        self.original_name = None;
        self.compile_error = None;
    }

    // ── Shader-specific ─────────────────────────────────────

    fn load_effect(&mut self, effect: &EffectDefinition) {
        self.mode = EditorMode::Shader;
        self.asset_name = effect.name.clone();
        self.code = effect.shader_code.clone();
        if effect.category == EffectCategory::Custom {
            self.editing_effect_id = Some(effect.id);
        } else {
            self.editing_effect_id = None;
        }
        self.original_code = Some(effect.shader_code.clone());
        self.original_name = Some(effect.name.clone());
        self.compile_error = None;
    }

    fn lookup_effect(
        &self,
        effect_id: Uuid,
        document: &lightningbeam_core::document::Document,
    ) -> Option<EffectDefinition> {
        use lightningbeam_core::effect_registry::EffectRegistry;
        if let Some(def) = document.effect_definitions.get(&effect_id) {
            return Some(def.clone());
        }
        EffectRegistry::get_by_id(&effect_id)
    }

    fn save_effect(&mut self, shared: &mut SharedPaneState) -> bool {
        if self.asset_name.trim().is_empty() {
            self.compile_error = Some("Name cannot be empty".to_string());
            return false;
        }
        let effect = if let Some(existing_id) = self.editing_effect_id {
            EffectDefinition::with_id(
                existing_id, self.asset_name.clone(),
                EffectCategory::Custom, self.code.clone(), vec![],
            )
        } else {
            EffectDefinition::new(
                self.asset_name.clone(), EffectCategory::Custom,
                self.code.clone(), vec![],
            )
        };
        let effect_id = effect.id;
        shared.action_executor.document_mut().add_effect_definition(effect);
        self.editing_effect_id = Some(effect_id);
        self.mark_saved_state();
        shared.effect_thumbnails_to_invalidate.push(effect_id);
        self.compile_error = None;
        true
    }

    // ── BeamDSP-specific ────────────────────────────────────

    fn load_script(&mut self, script: &ScriptDefinition) {
        self.mode = EditorMode::BeamDSP;
        self.asset_name = script.name.clone();
        self.code = script.source.clone();
        self.editing_script_id = Some(script.id);
        self.original_code = Some(script.source.clone());
        self.original_name = Some(script.name.clone());
        self.compile_error = None;
    }

    fn save_script(&mut self, shared: &mut SharedPaneState) -> bool {
        if self.asset_name.trim().is_empty() {
            self.compile_error = Some("Name cannot be empty".to_string());
            return false;
        }

        // Compile first — reject if invalid
        if let Err(err) = beamdsp::compile(&self.code) {
            self.compile_error = Some(format!("{}", err));
            return false;
        }

        let script = if let Some(existing_id) = self.editing_script_id {
            ScriptDefinition::with_id(existing_id, self.asset_name.clone(), self.code.clone())
        } else {
            ScriptDefinition::new(self.asset_name.clone(), self.code.clone())
        };
        let script_id = script.id;
        shared.action_executor.document_mut().add_script_definition(script);
        self.editing_script_id = Some(script_id);
        self.mark_saved_state();
        self.compile_error = None;

        // Auto-recompile: notify all Script nodes referencing this script
        *shared.script_saved = Some(script_id);
        true
    }

    // ── Dialog rendering ────────────────────────────────────

    fn render_unsaved_dialog(&mut self, ui: &mut egui::Ui) -> Option<UnsavedDialogResult> {
        let mut result = None;
        if self.show_unsaved_dialog {
            egui::Window::new("Unsaved Changes")
                .id(egui::Id::new("script_unsaved_dialog"))
                .collapsible(false)
                .resizable(false)
                .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0))
                .show(ui.ctx(), |ui| {
                    ui.set_min_width(300.0);
                    let label = match self.mode {
                        EditorMode::Shader => "You have unsaved changes to this shader.",
                        EditorMode::BeamDSP => "You have unsaved changes to this script.",
                    };
                    ui.label(label);
                    ui.label("What would you like to do?");
                    ui.add_space(12.0);
                    ui.horizontal(|ui| {
                        if ui.button("Cancel").clicked() {
                            result = Some(UnsavedDialogResult::Cancel);
                        }
                        if ui.button("Discard Changes").clicked() {
                            result = Some(UnsavedDialogResult::Discard);
                        }
                        if ui.button("Save & Continue").clicked() {
                            result = Some(UnsavedDialogResult::SaveAndContinue);
                        }
                    });
                });
        }
        result
    }

    // ── Toolbar rendering ───────────────────────────────────

    fn render_toolbar(
        &mut self,
        ui: &mut Ui,
        available_scripts: &[(Uuid, String)],
    ) -> (bool, bool, Option<Uuid>) {
        let mut save_clicked = false;
        let mut export_clicked = false;
        let mut open_script_id = None;
        ui.horizontal(|ui| {
            if ui.button("New").clicked() {
                self.new_asset();
            }

            // Open dropdown for existing scripts (BeamDSP mode)
            if self.mode == EditorMode::BeamDSP && !available_scripts.is_empty() {
                let open_btn = ui.button("Open");
                let popup_id = egui::Popup::default_response_id(&open_btn);
                egui::Popup::from_toggle_button_response(&open_btn)
                    .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
                    .show(|ui| {
                        ui.set_min_width(160.0);
                        for (id, name) in available_scripts {
                            let is_current = self.editing_script_id == Some(*id);
                            if ui.selectable_label(is_current, name).clicked() {
                                open_script_id = Some(*id);
                                egui::Popup::close_id(ui.ctx(), popup_id);
                            }
                        }
                    });
            }

            ui.separator();

            ui.label("Name:");
            ui.add(egui::TextEdit::singleline(&mut self.asset_name).desired_width(150.0));
            ui.separator();

            // Mode-specific templates
            match self.mode {
                EditorMode::Shader => {
                    egui::ComboBox::from_label("Template")
                        .selected_text("Insert Template")
                        .show_ui(ui, |ui| {
                            if ui.selectable_label(false, "Basic (Passthrough)").clicked() {
                                self.code = DEFAULT_SHADER_TEMPLATE.to_string();
                            }
                            if ui.selectable_label(false, "Grayscale").clicked() {
                                self.code = GRAYSCALE_TEMPLATE.to_string();
                            }
                            if ui.selectable_label(false, "Vignette").clicked() {
                                self.code = VIGNETTE_TEMPLATE.to_string();
                            }
                        });
                }
                EditorMode::BeamDSP => {
                    egui::ComboBox::from_label("Template")
                        .selected_text("Insert Template")
                        .show_ui(ui, |ui| {
                            if ui.selectable_label(false, "Passthrough").clicked() {
                                self.code = BEAMDSP_PASSTHROUGH.to_string();
                            }
                            if ui.selectable_label(false, "Simple Gain").clicked() {
                                self.code = BEAMDSP_GAIN.to_string();
                            }
                            if ui.selectable_label(false, "Stereo Delay").clicked() {
                                self.code = BEAMDSP_STEREO_DELAY.to_string();
                            }
                            if ui.selectable_label(false, "Custom LFO").clicked() {
                                self.code = BEAMDSP_LFO.to_string();
                            }
                        });
                }
            }
            ui.separator();

            if ui.button("Save").clicked() {
                save_clicked = true;
            }

            if self.mode == EditorMode::BeamDSP {
                if ui.button("Export").clicked() {
                    export_clicked = true;
                }
            }

            if self.has_unsaved_changes() {
                ui.label(egui::RichText::new("*").color(egui::Color32::YELLOW));
            }
            if self.is_editing_existing() {
                ui.label(egui::RichText::new("(Editing)").weak());
            } else {
                ui.label(egui::RichText::new("(New)").weak());
            }
        });
        (save_clicked, export_clicked, open_script_id)
    }

    fn render_error_panel(&self, ui: &mut Ui) {
        if let Some(error) = &self.compile_error {
            ui.horizontal(|ui| {
                ui.label(egui::RichText::new("Error:").color(egui::Color32::RED));
                ui.label(error);
            });
            ui.separator();
        }
    }
}

impl PaneRenderer for ShaderEditorPane {
    fn render_content(
        &mut self,
        ui: &mut egui::Ui,
        rect: egui::Rect,
        path: &NodePath,
        shared: &mut SharedPaneState,
    ) {
        // Handle effect loading request from asset library
        if let Some(effect_id) = shared.effect_to_load.take() {
            if let Some(effect) = self.lookup_effect(effect_id, shared.action_executor.document()) {
                if self.has_unsaved_changes() {
                    self.pending_load_effect = Some(effect);
                    self.show_unsaved_dialog = true;
                } else {
                    self.load_effect(&effect);
                }
            }
        }

        // Handle script loading request from node graph
        if let Some(script_id) = shared.script_to_edit.take() {
            if let Some(script) = shared.action_executor.document().get_script_definition(&script_id).cloned() {
                if self.has_unsaved_changes() {
                    self.pending_load_script = Some(script);
                    self.show_unsaved_dialog = true;
                } else {
                    self.load_script(&script);
                }
            }
        }

        // Handle unsaved changes dialog
        if let Some(result) = self.render_unsaved_dialog(ui) {
            match result {
                UnsavedDialogResult::Cancel => {
                    self.pending_load_effect = None;
                    self.pending_load_script = None;
                    self.show_unsaved_dialog = false;
                }
                UnsavedDialogResult::Discard => {
                    if let Some(effect) = self.pending_load_effect.take() {
                        self.load_effect(&effect);
                    }
                    if let Some(script) = self.pending_load_script.take() {
                        self.load_script(&script);
                    }
                    self.show_unsaved_dialog = false;
                }
                UnsavedDialogResult::SaveAndContinue => {
                    match self.mode {
                        EditorMode::Shader => { self.save_effect(shared); }
                        EditorMode::BeamDSP => { self.save_script(shared); }
                    }
                    if let Some(effect) = self.pending_load_effect.take() {
                        self.load_effect(&effect);
                    }
                    if let Some(script) = self.pending_load_script.take() {
                        self.load_script(&script);
                    }
                    self.show_unsaved_dialog = false;
                }
            }
        }

        // Background
        ui.painter().rect_filled(rect, 0.0, egui::Color32::from_rgb(25, 25, 30));

        let content_rect = rect.shrink(8.0);
        let mut content_ui = ui.new_child(
            egui::UiBuilder::new()
                .max_rect(content_rect)
                .layout(egui::Layout::top_down(egui::Align::LEFT)),
        );
        content_ui.set_min_width(content_rect.width() - 16.0);

        // Mode selector
        content_ui.horizontal(|ui| {
            if ui.selectable_value(&mut self.mode, EditorMode::Shader, "Shader").changed() {
                // Switching modes - reset to defaults for the new mode
                self.new_asset();
            }
            if ui.selectable_value(&mut self.mode, EditorMode::BeamDSP, "BeamDSP").changed() {
                self.new_asset();
            }
        });
        content_ui.add_space(2.0);

        // Collect available scripts for the Open dropdown
        let available_scripts: Vec<(Uuid, String)> = shared.action_executor.document()
            .script_definitions()
            .map(|s| (s.id, s.name.clone()))
            .collect();

        // Toolbar
        let (save_clicked, export_clicked, open_script_id) = self.render_toolbar(&mut content_ui, &available_scripts);
        content_ui.add_space(4.0);
        content_ui.separator();
        content_ui.add_space(4.0);

        // Handle open script
        if let Some(script_id) = open_script_id {
            if let Some(script) = shared.action_executor.document().get_script_definition(&script_id).cloned() {
                if self.has_unsaved_changes() {
                    self.pending_load_script = Some(script);
                    self.show_unsaved_dialog = true;
                } else {
                    self.load_script(&script);
                }
            }
        }

        // Handle save
        if save_clicked {
            match self.mode {
                EditorMode::Shader => { self.save_effect(shared); }
                EditorMode::BeamDSP => { self.save_script(shared); }
            }
        }

        // Handle export (.bdsp)
        if export_clicked {
            let default_name = format!("{}.bdsp", self.asset_name.trim());
            if let Some(path) = rfd::FileDialog::new()
                .set_title("Export BeamDSP Script")
                .set_file_name(&default_name)
                .add_filter("BeamDSP Script", &["bdsp"])
                .save_file()
            {
                if let Err(e) = std::fs::write(&path, &self.code) {
                    self.compile_error = Some(format!("Export failed: {}", e));
                }
            }
        }

        // Error panel
        self.render_error_panel(&mut content_ui);

        // Code editor
        let remaining_rect = content_ui.available_rect_before_wrap();
        let syntax = match self.mode {
            EditorMode::Shader => wgsl_syntax(),
            EditorMode::BeamDSP => beamdsp_syntax(),
        };

        egui::ScrollArea::both()
            .id_salt(("script_editor_scroll", path))
            .auto_shrink([false, false])
            .show(&mut content_ui, |ui| {
                ui.set_min_size(remaining_rect.size());
                CodeEditor::default()
                    .id_source("script_code_editor")
                    .with_rows(50)
                    .with_fontsize(13.0)
                    .with_theme(ColorTheme::GRUVBOX_DARK)
                    .with_syntax(syntax)
                    .with_numlines(true)
                    .show(ui, &mut self.code);
            });
    }

    fn name(&self) -> &str {
        match self.mode {
            EditorMode::Shader => "Shader Editor",
            EditorMode::BeamDSP => "Script Editor",
        }
    }
}
