use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;

use crossbeam_channel::Receiver;
use doux::EngineMetrics;

use crate::app::App;
use crate::engine::{
    build_stream, preload_sample_heads, spawn_sequencer, AnalysisHandle, AudioStreamConfig,
    LinkState, MidiCommand, PatternChange, ScopeBuffer, SequencerConfig, SequencerHandle,
    SpectrumBuffer,
};
use crate::midi;
use crate::model;
use crate::settings::Settings;
use crate::state::audio::RefreshRate;
use crate::state::StagedChange;
use crate::theme;

pub struct InitArgs {
    pub samples: Vec<PathBuf>,
    pub output: Option<String>,
    pub input: Option<String>,
    pub channels: Option<u16>,
    pub buffer: Option<u32>,
}

// Fields destructured in main.rs (cli) and plugin crate — all are used.
#[allow(dead_code)]
pub struct Init {
    pub app: App,
    pub link: Arc<LinkState>,
    pub sequencer: SequencerHandle,
    pub playing: Arc<AtomicBool>,
    pub nudge_us: Arc<AtomicI64>,
    pub metrics: Arc<EngineMetrics>,
    pub scope_buffer: Arc<ScopeBuffer>,
    pub spectrum_buffer: Arc<SpectrumBuffer>,
    pub audio_sample_pos: Arc<AtomicU64>,
    pub sample_rate_shared: Arc<AtomicU32>,
    pub stream: Option<cpal::Stream>,
    pub analysis_handle: Option<AnalysisHandle>,
    pub midi_rx: Receiver<MidiCommand>,
    pub stream_error_rx: crossbeam_channel::Receiver<String>,
    #[cfg(feature = "desktop")]
    pub settings: Settings,
    #[cfg(feature = "desktop")]
    pub mouse_x: Arc<AtomicU32>,
    #[cfg(feature = "desktop")]
    pub mouse_y: Arc<AtomicU32>,
    #[cfg(feature = "desktop")]
    pub mouse_down: Arc<AtomicU32>,
}

pub fn init(args: InitArgs) -> Init {
    let settings = Settings::load();

    let link = Arc::new(LinkState::new(settings.link.tempo, settings.link.quantum));
    if settings.link.enabled {
        link.enable();
    }

    let playing = Arc::new(AtomicBool::new(true));
    let nudge_us = Arc::new(AtomicI64::new(0));

    let mut app = App::new();

    app.ui.load_demo_on_startup = settings.display.load_demo_on_startup;
    app.ui.demo_index = settings.display.demo_index;

    if app.ui.load_demo_on_startup && !model::demos::DEMOS.is_empty() {
        let count = model::demos::DEMOS.len();
        let index = settings.display.demo_index % count;
        let demo = &model::demos::DEMOS[index];
        if let Ok(project) = model::load_str(demo.json) {
            let playing = project.playing_patterns.clone();
            let tempo = project.tempo;
            app.project_state.project = project;
            link.set_tempo(tempo);
            for (bank, pattern) in playing {
                app.playback.queued_changes.push(StagedChange {
                    change: PatternChange::Start { bank, pattern },
                    quantization: model::LaunchQuantization::Immediate,
                    sync_mode: model::SyncMode::PhaseLock,
                });
            }
            app.ui.set_status(format!("Demo: {}", demo.name));
        }
        app.ui.demo_index = (index + 1) % count;
    } else {
        app.playback.queued_changes.push(StagedChange {
            change: PatternChange::Start {
                bank: 0,
                pattern: 0,
            },
            quantization: model::LaunchQuantization::Immediate,
            sync_mode: model::SyncMode::PhaseLock,
        });
    }

    app.audio.config.output_device = args.output.or(settings.audio.output_device.clone());
    app.audio.config.input_device = args.input.or(settings.audio.input_device.clone());
    app.audio.config.channels = args.channels.unwrap_or(settings.audio.channels);
    app.audio.config.buffer_size = args.buffer.unwrap_or(settings.audio.buffer_size);
    app.audio.config.max_voices = settings.audio.max_voices;
    app.audio.config.sample_paths = if args.samples.is_empty() {
        settings.audio.sample_paths.clone()
    } else {
        args.samples
    };
    app.audio.config.refresh_rate = RefreshRate::from_fps(settings.display.fps);
    app.ui.runtime_highlight = settings.display.runtime_highlight;
    app.audio.config.show_scope = settings.display.show_scope;
    app.audio.config.show_spectrum = settings.display.show_spectrum;
    app.audio.config.show_lissajous = settings.display.show_lissajous;
    app.audio.config.show_preview = settings.display.show_preview;
    app.audio.config.gain_boost = settings.display.gain_boost;
    app.audio.config.normalize_viz = settings.display.normalize_viz;
    app.ui.show_completion = settings.display.show_completion;
    app.ui.performance_mode = settings.display.performance_mode;
    app.ui.color_scheme = settings.display.color_scheme;
    app.ui.hue_rotation = settings.display.hue_rotation;
    app.audio.config.layout = settings.display.layout;
    app.ui.onboarding_dismissed = settings.display.onboarding_dismissed.clone();
    app.ui.font = settings.display.font.clone();
    app.ui.zoom_factor = settings.display.zoom_factor;

    let palette = settings.display.color_scheme.to_palette();
    let rotated =
        cagire_ratatui::theme::transform::rotate_palette(&palette, settings.display.hue_rotation);
    theme::set(rotated);

    // MIDI connections
    let outputs = midi::list_midi_outputs();
    let inputs = midi::list_midi_inputs();
    for (slot, name) in settings.midi.output_devices.iter().enumerate() {
        if !name.is_empty() {
            if let Some(idx) = outputs.iter().position(|d| &d.name == name) {
                let _ = app.midi.connect_output(slot, idx);
            }
        }
    }
    for (slot, name) in settings.midi.input_devices.iter().enumerate() {
        if !name.is_empty() {
            if let Some(idx) = inputs.iter().position(|d| &d.name == name) {
                let _ = app.midi.connect_input(slot, idx);
            }
        }
    }

    let metrics = Arc::new(EngineMetrics::default());
    let scope_buffer = Arc::new(ScopeBuffer::new());
    let spectrum_buffer = Arc::new(SpectrumBuffer::new());

    let audio_sample_pos = Arc::new(AtomicU64::new(0));
    let sample_rate_shared = Arc::new(AtomicU32::new(44100));
    let mut initial_samples = Vec::new();
    for path in &app.audio.config.sample_paths {
        let index = doux::sampling::scan_samples_dir(path);
        app.audio.config.sample_counts.push(index.len());
        initial_samples.extend(index);
    }
    let preload_entries: Vec<(String, std::path::PathBuf)> = initial_samples
        .iter()
        .map(|e| (e.name.clone(), e.path.clone()))
        .collect();

    #[cfg(feature = "desktop")]
    let mouse_x = Arc::new(AtomicU32::new(0.5_f32.to_bits()));
    #[cfg(feature = "desktop")]
    let mouse_y = Arc::new(AtomicU32::new(0.5_f32.to_bits()));
    #[cfg(feature = "desktop")]
    let mouse_down = Arc::new(AtomicU32::new(0.0_f32.to_bits()));

    let seq_config = SequencerConfig {
        audio_sample_pos: Arc::clone(&audio_sample_pos),
        sample_rate: Arc::clone(&sample_rate_shared),
        cc_access: Some(Arc::new(app.midi.cc_memory.clone()) as Arc<dyn model::CcAccess>),
        variables: Arc::clone(&app.variables),
        dict: Arc::clone(&app.dict),
        #[cfg(feature = "desktop")]
        mouse_x: Arc::clone(&mouse_x),
        #[cfg(feature = "desktop")]
        mouse_y: Arc::clone(&mouse_y),
        #[cfg(feature = "desktop")]
        mouse_down: Arc::clone(&mouse_down),
    };

    let (sequencer, initial_audio_rx, midi_rx) = spawn_sequencer(
        Arc::clone(&link),
        Arc::clone(&playing),
        settings.link.quantum,
        Arc::clone(&app.live_keys),
        Arc::clone(&nudge_us),
        seq_config,
    );

    let (stream_error_tx, stream_error_rx) = crossbeam_channel::bounded(16);

    let stream_config = AudioStreamConfig {
        output_device: app.audio.config.output_device.clone(),
        channels: app.audio.config.channels,
        buffer_size: app.audio.config.buffer_size,
        max_voices: app.audio.config.max_voices,
    };

    let (stream, analysis_handle) = match build_stream(
        &stream_config,
        initial_audio_rx,
        Arc::clone(&scope_buffer),
        Arc::clone(&spectrum_buffer),
        Arc::clone(&metrics),
        initial_samples,
        Arc::clone(&audio_sample_pos),
        stream_error_tx,
        &app.audio.config.sample_paths,
    ) {
        Ok((s, info, analysis, registry)) => {
            app.audio.config.sample_rate = info.sample_rate;
            app.audio.config.host_name = info.host_name;
            app.audio.config.channels = info.channels;
            sample_rate_shared.store(info.sample_rate as u32, Ordering::Relaxed);
            app.audio.sample_registry = Some(Arc::clone(&registry));

            if !preload_entries.is_empty() {
                let sr = info.sample_rate;
                std::thread::Builder::new()
                    .name("sample-preload".into())
                    .spawn(move || {
                        preload_sample_heads(preload_entries, sr, &registry);
                    })
                    .expect("failed to spawn preload thread");
            }

            (Some(s), Some(analysis))
        }
        Err(e) => {
            app.ui.set_status(format!("Audio failed: {e}"));
            app.audio.error = Some(e);
            (None, None)
        }
    };

    app.evaluate_prelude(&link);
    app.mark_all_patterns_dirty();

    Init {
        app,
        link,
        sequencer,
        playing,
        nudge_us,
        metrics,
        scope_buffer,
        spectrum_buffer,
        audio_sample_pos,
        sample_rate_shared,
        stream,
        analysis_handle,
        midi_rx,
        stream_error_rx,
        #[cfg(feature = "desktop")]
        settings,
        #[cfg(feature = "desktop")]
        mouse_x,
        #[cfg(feature = "desktop")]
        mouse_y,
        #[cfg(feature = "desktop")]
        mouse_down,
    }
}

