use std::{path::PathBuf, sync::{Arc, Mutex}};

use tauri_plugin_log::{Target, TargetKind};
use log::{trace, info, debug, warn, error};
use tracing_subscriber::EnvFilter;
use chrono::Local;
use tauri::{AppHandle, Manager, Url, WebviewUrl, WebviewWindowBuilder};

mod audio;
mod video;
mod video_server;


#[derive(Default)]
struct AppState {
  counter: u32,
}

// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

#[tauri::command]
fn trace(msg: String) {
  trace!("{}",msg);
}
#[tauri::command]
fn info(msg: String) {
  info!("{}",msg);
}
#[tauri::command]
fn debug(msg: String) {
  debug!("{}",msg);
}
#[tauri::command]
fn warn(msg: String) {
  warn!("{}",msg);
}
#[tauri::command]
fn error(msg: String) {
  error!("{}",msg);
}

#[tauri::command]
async fn open_folder_dialog(app: AppHandle, title: String) -> Result<Option<String>, String> {
    use tauri_plugin_dialog::DialogExt;

    let folder = app.dialog()
        .file()
        .set_title(&title)
        .blocking_pick_folder();

    Ok(folder.map(|path| path.to_string()))
}

#[tauri::command]
async fn read_folder_files(path: String) -> Result<Vec<String>, String> {
    use std::fs;

    let entries = fs::read_dir(&path)
        .map_err(|e| format!("Failed to read directory: {}", e))?;

    let audio_extensions = vec!["wav", "aif", "aiff", "flac", "mp3", "ogg"];

    let mut files = Vec::new();
    for entry in entries {
        let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?;
        let path = entry.path();

        if path.is_file() {
            if let Some(ext) = path.extension() {
                let ext_str = ext.to_string_lossy().to_lowercase();
                if audio_extensions.contains(&ext_str.as_str()) {
                    if let Some(filename) = path.file_name() {
                        files.push(filename.to_string_lossy().to_string());
                    }
                }
            }
        }
    }

    Ok(files)
}

use tauri::PhysicalSize;

#[tauri::command]
async fn create_window(app: tauri::AppHandle, path: Option<String>) {
    let state = app.state::<Mutex<AppState>>();

    // Lock the mutex to get mutable access:
    let mut state = state.lock().unwrap();

    // Increment the counter and generate a unique window label
    let window_label = format!("window{}", state.counter);
    state.counter += 1;

    // Build the new window with the unique label
    let webview_window = WebviewWindowBuilder::new(&app, &window_label, WebviewUrl::App("index.html".into()))
        .title("Lightningbeam")
        .build()
        .unwrap();

    // Get the current monitor's screen size from the new window
    if let Ok(Some(monitor)) = webview_window.current_monitor() {
        let screen_size = monitor.size(); // Get the size of the monitor
        let width = 4096;
        let height = 4096;

        // Set the window size to be the smaller of the specified size or the screen size
        let new_width = width.min(screen_size.width as u32);
        let new_height = height.min(screen_size.height as u32 - 100);

        // Set the size using PhysicalSize
        webview_window.set_size(tauri::Size::Physical(PhysicalSize::new(new_width, new_height)))
            .expect("Failed to set window size");
    } else {
        eprintln!("Could not detect the current monitor.");
    }

    // Set the opened file if provided
    if let Some(val) = path {
        // Pass path data to the window via JavaScript
        webview_window.eval(&format!("window.openedFiles = [\"{val}\"]")).unwrap();

        // Set the window title if provided
        webview_window.set_title(&val).expect("Failed to set window title");
    }
}


fn handle_file_associations(app: AppHandle, files: Vec<PathBuf>) {
  // -- Scope handling start --

  // You can remove this block if you only want to know about the paths, but not actually "use" them in the frontend.

  // This requires the `fs` tauri plugin and is required to make the plugin's frontend work:
  use tauri_plugin_fs::FsExt;
  let fs_scope = app.fs_scope();

  // This is for the `asset:` protocol to work:
  let asset_protocol_scope = app.asset_protocol_scope();

  for file in &files {
    // This requires the `fs` plugin:
    let _ = fs_scope.allow_file(file);

    // This is for the `asset:` protocol:
    let _ = asset_protocol_scope.allow_file(file);
  }

  // -- Scope handling end --

  let files = files
    .into_iter()
    .map(|f| {
      let file = f.to_string_lossy().replace('\\', "\\\\"); // escape backslash
      format!("\"{file}\"",) // wrap in quotes for JS array
    })
    .collect::<Vec<_>>()
    .join(",");
  warn!("{}",files);

  let window = app.get_webview_window("main").unwrap();
  window.eval(&format!("window.openedFiles = [{files}]")).unwrap();
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let pkg_name = env!("CARGO_PKG_NAME").to_string();
    // Initialize video HTTP server
    let video_server = video_server::VideoServer::new()
        .expect("Failed to start video server");
    eprintln!("[App] Video server started on port {}", video_server.port());

    tauri::Builder::default()
      .manage(Mutex::new(AppState::default()))
      .manage(Arc::new(Mutex::new(audio::AudioState::default())))
      .manage(Arc::new(Mutex::new(video::VideoState::default())))
      .manage(Arc::new(Mutex::new(video_server)))
      .setup(|app| {
        #[cfg(any(windows, target_os = "linux"))] // Windows/Linux needs different handling from macOS
        {
          let mut files = Vec::new();

          // NOTICE: `args` may include URL protocol (`your-app-protocol://`)
          // or arguments (`--`) if your app supports them.
          // files may also be passed as `file://path/to/file`
          for maybe_file in std::env::args().skip(1) {
            // skip flags like -f or --flag
            if maybe_file.starts_with('-') {
              continue;
            }

            // handle `file://` path urls and skip other urls
            if let Ok(url) = Url::parse(&maybe_file) {
            // if let Ok(url) = url::Url::parse(&maybe_file) {
              if let Ok(path) = url.to_file_path() {
                files.push(path);
              }
            } else {
              files.push(PathBuf::from(maybe_file))
            }
          }

          handle_file_associations(app.handle().clone(), files);
        }
        #[cfg(debug_assertions)] // only include this code on debug builds
        {
          let window = app.get_webview_window("main").unwrap();
          window.open_devtools();
          window.close_devtools();
        }
        Ok(())
      })
      .plugin(
          tauri_plugin_log::Builder::new()
              .timezone_strategy(tauri_plugin_log::TimezoneStrategy::UseLocal)
              .format(|out, message, record| {
                  let date = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
                  out.finish(format_args!(
                      "{}[{}] {}",
                      date,
                      record.level(),
                      message
                    ))
                })
              .targets([
                  Target::new(TargetKind::Stdout),
                  // LogDir locations:
                  // Linux: /home/user/.local/share/org.lightningbeam.core/logs
                  // macOS: /Users/user/Library/Logs/org.lightningbeam.core/logs
                  // Windows: C:\Users\user\AppData\Local\org.lightningbeam.core\logs
                  Target::new(TargetKind::LogDir { file_name: Some("logs".to_string()) }),
                  Target::new(TargetKind::Webview),
              ])
              .build()
      )
      .plugin(tauri_plugin_dialog::init())
      .plugin(tauri_plugin_fs::init())
      .plugin(tauri_plugin_shell::init())
      .invoke_handler(tauri::generate_handler![
        greet, trace, debug, info, warn, error, create_window,
        audio::audio_init,
        audio::audio_reset,
        audio::audio_play,
        audio::audio_stop,
        audio::set_metronome_enabled,
        audio::audio_seek,
        audio::audio_test_beep,
        audio::audio_set_track_parameter,
        audio::audio_create_track,
        audio::audio_load_file,
        audio::audio_add_clip,
        audio::audio_move_clip,
        audio::audio_trim_clip,
        audio::audio_extend_clip,
        audio::audio_start_recording,
        audio::audio_stop_recording,
        audio::audio_pause_recording,
        audio::audio_resume_recording,
        audio::audio_start_midi_recording,
        audio::audio_stop_midi_recording,
        audio::audio_create_midi_clip,
        audio::audio_add_midi_note,
        audio::audio_load_midi_file,
        audio::audio_get_midi_clip_data,
        audio::audio_update_midi_clip_notes,
        audio::audio_send_midi_note_on,
        audio::audio_send_midi_note_off,
        audio::audio_set_active_midi_track,
        audio::audio_get_pool_file_info,
        audio::audio_get_pool_waveform,
        audio::graph_add_node,
        audio::graph_add_node_to_template,
        audio::graph_remove_node,
        audio::graph_connect,
        audio::graph_connect_in_template,
        audio::graph_disconnect,
        audio::graph_set_parameter,
        audio::graph_set_output_node,
        audio::graph_save_preset,
        audio::graph_load_preset,
        audio::graph_load_preset_from_json,
        audio::graph_list_presets,
        audio::graph_delete_preset,
        audio::graph_get_state,
        audio::graph_get_template_state,
        audio::sampler_load_sample,
        audio::multi_sampler_add_layer,
        audio::multi_sampler_get_layers,
        audio::multi_sampler_update_layer,
        audio::multi_sampler_remove_layer,
        audio::get_oscilloscope_data,
        audio::automation_add_keyframe,
        audio::automation_remove_keyframe,
        audio::automation_get_keyframes,
        audio::automation_set_name,
        audio::automation_get_name,
        audio::audio_serialize_pool,
        audio::audio_load_pool,
        audio::audio_resolve_missing_file,
        audio::audio_serialize_track_graph,
        audio::audio_load_track_graph,
        audio::audio_export,
        video::video_load_file,
        video::video_get_frame,
        video::video_get_frames_batch,
        video::video_set_cache_size,
        open_folder_dialog,
        read_folder_files,
        video::video_get_pool_info,
        video::video_ipc_benchmark,
        video::video_get_transcode_status,
        video::video_allow_asset,
      ])
      // .manage(window_counter)
      .build(tauri::generate_context!())
      .expect("error while running tauri application")
      .run(
        #[allow(unused_variables)]
        |app, event| {
          #[cfg(any(target_os = "macos", target_os = "ios"))]
          if let tauri::RunEvent::Opened { urls } = event {
            let app = app.clone();
            let files = urls
              .into_iter()
              .filter_map(|url| url.to_file_path().ok())
              .map(|f| {
                let file = f.to_string_lossy().replace('\\', "\\\\"); // escape backslash
                format!("\"{file}\"",) // wrap in quotes for JS array
              })
              .collect::<Vec<_>>();
  
            tauri::async_runtime::spawn(async move {
              for path in files {
                create_window(app.clone(), Some(path)).await;
              }
            });
          }
        },
      );
    tracing_subscriber::fmt().with_env_filter(EnvFilter::new(format!("{}=trace", pkg_name))).init();
}
