use std::{mem, time::{Duration, Instant}};

use anyhow::Result;
use futures::{StreamExt, stream::FuturesUnordered};
use hashbrown::HashSet;
use yazi_core::mgr::OpenOpt;
use yazi_fs::{FsAuth, FsUrl, engine::{Engine, local::Local}};
use yazi_macro::succ;
use yazi_parser::mgr::DownloadForm;
use yazi_proxy::MgrProxy;
use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
use yazi_vfs::engine;

use crate::{Actor, Ctx};

pub struct Download;

impl Actor for Download {
	type Form = DownloadForm;

	const NAME: &str = "download";

	fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
		let cwd = cx.cwd().clone();
		let scheduler = cx.tasks.scheduler.clone();

		tokio::spawn(async move {
			Self::prepare(&form.urls).await;

			let mut wg1 = FuturesUnordered::new();
			for url in form.urls {
				let done = scheduler.file_download(url.clone());
				wg1.push(async move { (done.future().await, url) });
			}

			let mut wg2 = vec![];
			let mut urls = Vec::with_capacity(wg1.len());
			let mut files = Vec::with_capacity(wg1.len());
			let mut instant = Instant::now();
			while let Some((success, url)) = wg1.next().await {
				if !success {
					continue;
				}

				let Ok(f) = engine::file(&url).await else { continue };
				urls.push(url);
				files.push(f);

				if instant.elapsed() >= Duration::from_secs(1) {
					wg2.push(scheduler.fetch_mimetype(mem::take(&mut files)));
					instant = Instant::now();
				}
			}

			if !files.is_empty() {
				wg2.push(scheduler.fetch_mimetype(files));
			}
			if futures::future::join_all(wg2).await.into_iter().any(|b| !b) {
				return;
			}
			if form.open && !urls.is_empty() {
				MgrProxy::open(OpenOpt {
					cwd:         Some(cwd),
					targets:     urls,
					interactive: false,
					hovered:     false,
				});
			}
		});

		succ!();
	}
}

impl Download {
	async fn prepare(urls: &[UrlBuf]) {
		let stamp_roots = urls.iter().filter_map(|u| u.auth().stamp_root());
		let bucket_dirs = urls.iter().filter_map(|u| u.parent()?.cache_bucket());

		let dirs: HashSet<_> = stamp_roots.chain(bucket_dirs).collect();
		for dir in dirs {
			Local::regular(&dir).create_dir_all().await.ok();
		}
	}
}
