use std::ops::Range;

pub struct AssetUrlIter<'a>(&'a str);

#[derive(Debug)]
pub enum AssetUrlItem<'a> {
  Asset((Range<u32>, &'a str, Option<&'a str>)),
  PublicAsset((Range<u32>, &'a str)),
}

impl<'a> From<&'a str> for AssetUrlIter<'a> {
  fn from(code: &'a str) -> Self {
    AssetUrlIter(code)
  }
}

impl<'a> AssetUrlIter<'a> {
  #[expect(clippy::cast_possible_truncation)]
  pub fn into_asset_url_iter(&self) -> impl Iterator<Item = AssetUrlItem<'a>> {
    self.0.match_indices("__VITE_ASSET_").filter_map(|(start, _)| {
      if self.0[start + 13..].starts_with('_') {
        let hash_s = start + 14;
        let hash_e = hash_s + self.0[hash_s..].find("__")?;
        let reference_id = &self.0[hash_s..hash_e];
        let mut end = reference_id
          .bytes()
          .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'$')
          .then_some(hash_e + 2)?;
        let postfix = self.0[end..].starts_with("$_").then(|| {
          self.0[end + 2..].find("__").map_or("", |i| {
            let v = &self.0[end + 2..end + 2 + i];
            end = end + 2 + i + 2;
            v
          })
        });
        return Some(AssetUrlItem::Asset(((start as u32)..(end as u32), reference_id, postfix)));
      }
      if self.0[start + 13..].starts_with("PUBLIC__") {
        let hash_s = start + 21;
        let hash_e = hash_s + 8;
        let hash = self.0.get(hash_s..hash_e)?;
        if self.0.get(hash_e..hash_e + 2)? != "__" {
          return None;
        }
        return hash
          .bytes()
          .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
          .then_some(AssetUrlItem::PublicAsset(((start as u32)..((hash_e + 2) as u32), hash)));
      }
      None
    })
  }
}

#[test]
fn test_into_asset_url_iter() {
  let result: Vec<_> = AssetUrlIter::from(
    "__VITE_ASSET__adwA22929_$__;__VITE_ASSET__333__$_?666__;__VITE_ASSET_PUBLIC__12345678__",
  )
  .into_asset_url_iter()
  .map(|item| match item {
    AssetUrlItem::Asset((range, hash, postfix)) => (range, hash, postfix),
    AssetUrlItem::PublicAsset((range, hash)) => (range, hash, None),
  })
  .collect();
  assert_eq!(
    result,
    vec![
      (0u32..27, "adwA22929_$", None),
      (28u32..55, "333", Some("?666")),
      (56u32..87, "12345678", None)
    ]
  );
}
