mod split;

use async_trait::async_trait;
use rspack_paths::{Utf8Path, Utf8PathBuf};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
pub use split::SplitPackStrategy;

use super::data::{
  Pack, PackContents, PackFileMeta, PackGenerations, PackKeys, PackOptions, PackScope, RootMeta,
  RootOptions,
};
use crate::{
  error::{Result, ValidateResult},
  ItemKey, ItemValue,
};

pub struct UpdatePacksResult {
  pub new_packs: Vec<(PackFileMeta, Pack)>,
  pub remain_packs: Vec<(PackFileMeta, Pack)>,
  pub removed_files: Vec<Utf8PathBuf>,
}

#[async_trait]
pub trait ScopeStrategy:
  RootStrategy
  + ScopeReadStrategy
  + ScopeWriteStrategy
  + ScopeValidateStrategy
  + std::fmt::Debug
  + Sync
  + Send
{
}

#[async_trait]
pub trait RootStrategy {
  async fn before_load(&self) -> Result<()>;
  async fn read_root_meta(&self) -> Result<Option<RootMeta>>;
  async fn write_root_meta(&self, root_meta: &RootMeta) -> Result<()>;
  async fn validate_root(&self, root_meta: &RootMeta) -> Result<ValidateResult>;
  async fn clean(
    &self,
    root_meta: &RootMeta,
    scopes: &HashMap<String, PackScope>,
    root_options: &RootOptions,
  ) -> Result<()>;
}

#[derive(Debug, Default)]
pub struct PackMainContents {
  pub contents: PackContents,
  pub generations: PackGenerations,
}

#[async_trait]
pub trait PackReadStrategy {
  async fn read_pack_keys(&self, path: &Utf8Path) -> Result<Option<PackKeys>>;
  async fn read_pack_contents(&self, path: &Utf8Path) -> Result<Option<PackMainContents>>;
}

#[async_trait]
pub trait PackWriteStrategy {
  async fn update_packs(
    &self,
    dir: Utf8PathBuf,
    generation: usize,
    options: &PackOptions,
    packs: HashMap<PackFileMeta, Pack>,
    updates: HashMap<ItemKey, Option<ItemValue>>,
  ) -> Result<UpdatePacksResult>;
  async fn optimize_packs(
    &self,
    dir: Utf8PathBuf,
    options: &PackOptions,
    packs: Vec<(PackFileMeta, Pack)>,
  ) -> Result<UpdatePacksResult>;
  async fn write_pack(&self, pack: &Pack) -> Result<()>;
}

#[async_trait]
pub trait ScopeReadStrategy {
  fn get_path(&self, sub: &str) -> Utf8PathBuf;
  async fn ensure_meta(&self, scope: &mut PackScope) -> Result<()>;
  async fn ensure_packs(&self, scope: &mut PackScope) -> Result<()>;
  async fn ensure_keys(&self, scope: &mut PackScope) -> Result<()>;
  async fn ensure_contents(&self, scope: &mut PackScope) -> Result<()>;
}

#[async_trait]
pub trait ScopeValidateStrategy {
  async fn validate_meta(&self, scope: &mut PackScope) -> Result<ValidateResult>;
  async fn validate_packs(&self, scope: &mut PackScope) -> Result<ValidateResult>;
}

#[derive(Debug, Default, Clone)]
pub struct WriteScopeResult {
  pub wrote_files: HashSet<Utf8PathBuf>,
  pub removed_files: HashSet<Utf8PathBuf>,
}

impl WriteScopeResult {
  pub fn extend(&mut self, other: Self) {
    self.wrote_files.extend(other.wrote_files);
    self.removed_files.extend(other.removed_files);
  }
}

pub type ScopeUpdate = HashMap<ItemKey, Option<ItemValue>>;
#[async_trait]
pub trait ScopeWriteStrategy {
  async fn update_scope(&self, scope: &mut PackScope, updates: ScopeUpdate) -> Result<()>;
  async fn before_all(&self, scopes: &mut HashMap<String, PackScope>) -> Result<()>;
  async fn optimize_scope(&self, scope: &mut PackScope) -> Result<()>;
  async fn write_packs(&self, scope: &mut PackScope) -> Result<WriteScopeResult>;
  async fn write_meta(&self, scope: &mut PackScope) -> Result<WriteScopeResult>;
  async fn merge_changed(&self, changed: WriteScopeResult) -> Result<()>;
  async fn after_all(&self, scopes: &mut HashMap<String, PackScope>) -> Result<()>;
}
