// Copyright 2018-2025 the Deno authors. MIT license.

use std::path::Path;

use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_lib::cache::DenoDir;

use crate::colors;
use crate::display;
use crate::sys::CliSys;
use crate::util::progress_bar::ProgressBar;
use crate::util::progress_bar::ProgressBarStyle;
use crate::util::progress_bar::ProgressMessagePrompt;
use crate::util::progress_bar::UpdateGuard;

struct CleanState {
  files_removed: u64,
  dirs_removed: u64,
  bytes_removed: u64,
  progress_guard: UpdateGuard,
}

impl CleanState {
  fn update_progress(&self) {
    self
      .progress_guard
      .set_position(self.files_removed + self.dirs_removed);
  }
}

pub fn clean() -> Result<(), AnyError> {
  let deno_dir = DenoDir::new(CliSys::default(), None)?;
  if deno_dir.root.exists() {
    let no_of_files = walkdir::WalkDir::new(&deno_dir.root).into_iter().count();
    let progress_bar = ProgressBar::new(ProgressBarStyle::ProgressBars);
    let progress_guard =
      progress_bar.update_with_prompt(ProgressMessagePrompt::Cleaning, "");

    let mut state = CleanState {
      files_removed: 0,
      dirs_removed: 0,
      bytes_removed: 0,
      progress_guard,
    };
    state
      .progress_guard
      .set_total_size(no_of_files.try_into().unwrap());

    rm_rf(&mut state, &deno_dir.root)?;

    // Drop the guard so that progress bar disappears.
    drop(state.progress_guard);

    log::info!(
      "{} {} {}",
      colors::green("Removed"),
      deno_dir.root.display(),
      colors::gray(&format!(
        "({} files, {})",
        state.files_removed + state.dirs_removed,
        display::human_size(state.bytes_removed as f64)
      ))
    );
  }

  Ok(())
}

fn rm_rf(state: &mut CleanState, path: &Path) -> Result<(), AnyError> {
  for entry in walkdir::WalkDir::new(path).contents_first(true) {
    let entry = entry?;

    if entry.file_type().is_dir() {
      state.dirs_removed += 1;
      state.update_progress();
      std::fs::remove_dir_all(entry.path())?;
    } else {
      remove_file(state, entry.path(), entry.metadata().ok())?;
    }
  }

  Ok(())
}

fn remove_file(
  state: &mut CleanState,
  path: &Path,
  meta: Option<std::fs::Metadata>,
) -> Result<(), AnyError> {
  if let Some(meta) = meta {
    state.bytes_removed += meta.len();
  }
  state.files_removed += 1;
  state.update_progress();
  std::fs::remove_file(path)
    .with_context(|| format!("Failed to remove file: {}", path.display()))?;
  Ok(())
}