use crate::resolve_url;
pub use sourcemap::SourceMap;
use std::borrow::Cow;
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,
{
  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)
  }
}
#[derive(Debug, Default)]
pub struct SourceMapCache {
  maps: HashMap<String, Option<SourceMap>>,
  source_lines: HashMap<(String, i64), Option<String>>,
  pub stashed_file_name: Option<String>,
}
pub enum SourceMapApplication {
  Unchanged,
  LineAndColumn {
    line_number: u32,
    column_number: u32,
  },
  LineAndColumnAndFileName {
    file_name: String,
    line_number: u32,
    column_number: u32,
  },
}
pub fn apply_source_map<G: SourceMapGetter + ?Sized>(
  file_name: &str,
  line_number: u32,
  column_number: u32,
  cache: &mut SourceMapCache,
  getter: &G,
) -> SourceMapApplication {
  let line_number = line_number - 1;
  let column_number = column_number - 1;
  let maybe_source_map_entry = cache.maps.get(file_name);
  let maybe_source_map = maybe_source_map_entry
    .map(Cow::Borrowed)
    .unwrap_or_else(|| {
      let maybe_source_map = getter
        .get_source_map(file_name)
        .and_then(|raw_source_map| SourceMap::from_slice(&raw_source_map).ok());
      Cow::Owned(maybe_source_map)
    });
  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 maybe_source_map {
    Cow::Borrowed(_) => {}
    Cow::Owned(maybe_source_map) => {
      cache.maps.insert(file_name.to_owned(), maybe_source_map);
    }
  }
  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<G: SourceMapGetter + ?Sized>(
  file_name: &str,
  line_number: i64,
  cache: &mut SourceMapCache,
  getter: &G,
) -> Option<String> {
  cache
    .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() <= MAX_SOURCE_LINE_LENGTH)
    })
    .clone()
}