use std::time::{Duration, SystemTime};

use hashbrown::HashMap;
use notify::Result;
use tokio::{pin, sync::mpsc::UnboundedReceiver};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_fs::{File, FilesOp};
use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_vfs::VfsFile;

use crate::{MgrProxy, Reporter, WATCHER, Watchee};

pub(crate) struct Remote;

impl Remote {
	pub(crate) fn serve(rx: UnboundedReceiver<(UrlBuf, bool)>, _reporter: Reporter) -> Self {
		tokio::spawn(Self::changed(rx));

		Self
	}

	pub(crate) fn watch(&mut self, _watchee: &mut Watchee) -> Result<()> { Ok(()) }

	pub(crate) fn unwatch(&mut self, _watchee: &Watchee) -> Result<()> { Ok(()) }

	async fn changed(rx: UnboundedReceiver<(UrlBuf, bool)>) {
		let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250));
		pin!(rx);

		while let Some(chunk) = rx.next().await {
			let mut urls = HashMap::with_capacity(chunk.len());
			for (u, upload) in chunk {
				urls.entry(u).and_modify(|b| *b |= upload).or_insert(upload);
			}

			let _permit = WATCHER.acquire().await.unwrap();

			let mut ops = Vec::with_capacity(urls.len());
			let mut ups = Vec::with_capacity(urls.len());

			for (u, upload) in urls {
				let Some((parent, urn)) = u.pair() else { continue };
				let Ok(mut file) = File::new(&u).await else {
					ops.push(FilesOp::Deleting(parent.into(), [urn.into()].into()));
					continue;
				};

				let is_file = file.is_file();
				file.cha.ctime = Some(SystemTime::now());

				ops.push(FilesOp::Upserting(parent.into(), [(urn.into(), file)].into()));
				if upload && is_file {
					ups.push(u);
				}
			}

			FilesOp::mutate(ops);
			MgrProxy::upload(ups);
		}
	}
}
