use crate::resolve_url;
pub use sourcemap::SourceMap;
use std::collections::HashMap;
use std::rc::Rc;
use std::str;
pub trait SourceMapGetter {
    fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>>;
  fn get_source_line(
    &self,
    file_name: &str,
    line_number: usize,
  ) -> Option<String>;
}
impl<T> SourceMapGetter for Rc<T>
where
  T: SourceMapGetter + ?Sized,
{
  fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
    (**self).get_source_map(file_name)
  }
  fn get_source_line(
    &self,
    file_name: &str,
    line_number: usize,
  ) -> Option<String> {
    (**self).get_source_line(file_name, line_number)
  }
}
pub enum SourceMapApplication {
    Unchanged,
    LineAndColumn {
    line_number: u32,
    column_number: u32,
  },
    LineAndColumnAndFileName {
    file_name: String,
    line_number: u32,
    column_number: u32,
  },
}
pub struct SourceMapper<G: SourceMapGetter> {
  maps: HashMap<String, Option<SourceMap>>,
  source_lines: HashMap<(String, i64), Option<String>>,
  getter: Option<G>,
  pub(crate) ext_source_maps: HashMap<String, Vec<u8>>,
      pub(crate) stashed_file_name: Option<String>,
}
impl<G: SourceMapGetter> SourceMapper<G> {
  pub fn new(getter: Option<G>) -> Self {
    Self {
      maps: Default::default(),
      source_lines: Default::default(),
      ext_source_maps: Default::default(),
      getter,
      stashed_file_name: Default::default(),
    }
  }
  pub fn has_user_sources(&self) -> bool {
    self.getter.is_some()
  }
            pub fn apply_source_map(
    &mut self,
    file_name: &str,
    line_number: u32,
    column_number: u32,
  ) -> SourceMapApplication {
        let line_number = line_number - 1;
    let column_number = column_number - 1;
    let getter = self.getter.as_ref();
    let maybe_source_map =
      self.maps.entry(file_name.to_owned()).or_insert_with(|| {
        None
          .or_else(|| {
            SourceMap::from_slice(self.ext_source_maps.get(file_name)?).ok()
          })
          .or_else(|| {
            SourceMap::from_slice(&getter?.get_source_map(file_name)?).ok()
          })
      });
    let Some(source_map) = maybe_source_map.as_ref() else {
      return SourceMapApplication::Unchanged;
    };
    let Some(token) = source_map.lookup_token(line_number, column_number)
    else {
      return SourceMapApplication::Unchanged;
    };
    let new_line_number = token.get_src_line() + 1;
    let new_column_number = token.get_src_col() + 1;
    let new_file_name = match token.get_source() {
      Some(source_file_name) => {
        if source_file_name == file_name {
          None
        } else {
                                                  match resolve_url(source_file_name) {
            Ok(m) if m.scheme() == "blob" => None,
            Ok(m) => Some(m.to_string()),
            Err(_) => None,
          }
        }
      }
      None => None,
    };
    match new_file_name {
      None => SourceMapApplication::LineAndColumn {
        line_number: new_line_number,
        column_number: new_column_number,
      },
      Some(file_name) => SourceMapApplication::LineAndColumnAndFileName {
        file_name,
        line_number: new_line_number,
        column_number: new_column_number,
      },
    }
  }
  const MAX_SOURCE_LINE_LENGTH: usize = 150;
  pub fn get_source_line(
    &mut self,
    file_name: &str,
    line_number: i64,
  ) -> Option<String> {
    let getter = self.getter.as_ref()?;
    self
      .source_lines
      .entry((file_name.to_string(), line_number))
      .or_insert_with(|| {
                let s = getter.get_source_line(file_name, (line_number - 1) as usize);
        s.filter(|s| s.len() <= Self::MAX_SOURCE_LINE_LENGTH)
      })
      .clone()
  }
}