//! Omitted Imports

#[derive(GodotClass)]
#[class(base = EditorImportPlugin, tool, init)]
pub struct AnimationImporter {
	base: Base<EditorImportPlugin>,
}

#[derive(GodotConvert, Export, Var, Default)]
#[godot(via = GString)]
pub enum AnimationNameStyle {
	#[default]
	SnakeCase,
	CamelCase,
}

#[derive(Clone, Deserialize)]
pub struct AnimationData<'import> {
	pub asset_path: &'import str,
	pub frame_size: i32,
	pub default_frame_time: f32,
	pub frame_time_override: HashMap<&'import str, f32>,
	pub should_loop: HashSet<&'import str>,
}

#[godot_api]
impl IEditorImportPlugin for AnimationImporter {
	fn get_importer_name(&self) -> GString {
		GString::from("cresthollow.imports.animation")
	}

	fn get_visible_name(&self) -> GString {
		GString::from("JSON Animation Library")
	}

	fn get_preset_count(&self) -> i32 {
		1
	}

	fn get_preset_name(&self, _preset_index: i32) -> GString {
		GString::from("Default")
	}

	fn get_recognized_extensions(&self) -> PackedStringArray {
		PackedStringArray::from(&[GString::from("animdata")])
	}

	fn get_import_options(&self, _path: GString, _preset_index: i32) -> Array<Dictionary> {
		array![
			&dict! {
				"name": "Outputs",
				"default_value": "",
				"usage": PropertyUsageFlags::GROUP,
			},
			&dict! {
				"name": "animation_name_style",
				"default_value": AnimationNameStyle::default(),
			},
		]
	}

	fn get_save_extension(&self) -> GString {
		GString::from("res")
	}

	fn get_resource_type(&self) -> GString {
		GString::from("AnimationLibrary")
	}

	fn get_priority(&self) -> f32 {
		2.0
	}

	fn get_import_order(&self) -> i32 {
		ImportOrder::SCENE.ord()
	}

	fn get_option_visibility(&self, _path: GString, _option_name: StringName, _options: Dictionary) -> bool {
		true
	}

	fn import(
		&self,
		source_file: GString,
		save_path: GString,
		_options: Dictionary,
		_platform_variants: Array<GString>,
		mut gen_files: Array<GString>,
	) -> Error {
		let output_path = GString::from(format!("{}.{}", save_path, self.get_save_extension()));
		let atlas_path = resolve_atlas_path(&PathBuf::from(source_file.to_string()));

		let resource = match FileAccess::open(&source_file, ModeFlags::READ) {
			Some(file) => file,
			None => {
				godot_error!("[Anim] Failed to open file: {}", source_file);
				return Error::ERR_FILE_CANT_OPEN;
			}
		};
		check_error!("[Anim] File Error: {:?}", resource.get_error());

		let buffer = resource.read_to_buffer();
		let animation_data: AnimationData = unwrap_error!(
			"[Anim] Invalid animation data found: {:?}",
			serde_json::from_slice(buffer.as_slice()).comp()
		);

		let mut animation_library = resolve_animation_library(&output_path);
		let sheet_folder =
			resolve_folder_path(&PathBuf::from(source_file.to_string()), animation_data.asset_path);
		let found_paths = resolve_sheet_paths(&sheet_folder);

		let atlas_stitcher = AtlasBatch::new(found_paths.as_slice());
		let atlas_spec: GeneratedAtlasSpec = unwrap_error!(
			"[Anim] Failed to create atlas layout: {:?}",
			atlas_stitcher.allocate_space()
		);
		let generated_atlas: DynamicImage = unwrap_error!(
			"[Anim] Failed to generate image atlas: {:?}",
			AtlasBatch::generate_atlas(&atlas_spec)
		);

		let system_atlas_path = ProjectSettings::singleton().globalize_path(&atlas_path.display().godot());
		let mut writer = match std::fs::File::create(system_atlas_path.to_string()) {
			Ok(file) => file,
			Err(err) => {
				godot_error!("[Anim] Failed to create output texture file: {}", err);
				return Error::ERR_FILE_CANT_OPEN;
			}
		};

		unwrap_error!(
			"[Anim] Failed to write generated atlas: {:?}",
			generated_atlas.write_to(&mut writer, ImageFormat::Png).comp()
		);

		gen_files.push(&atlas_path.display().godot());

		godot_print!(
			"[Anim] Created texture atlas with tile dimensions: {}x{}",
			atlas_spec.width / animation_data.frame_size,
			atlas_spec.height / animation_data.frame_size
		);

		build_animation_library(&mut animation_library, &animation_data, &atlas_spec);
		ResourceSaver::singleton()
			.save_ex(&animation_library)
			.path(&output_path)
			.done()
	}
}

fn resolve_sheet_paths(base_folder: &Path) -> Vec<String> {
	let mut dir = match DirAccess::open(&base_folder.display().godot()) {
		Some(value) => value,
		None => return Vec::new(),
	};
	dir.set_include_hidden(false);
	dir.set_include_navigational(false);

	let file_list = dir.get_files();
	file_list
		.as_slice()
		.iter()
		.filter_map(|filename| {
			if !filename.begins_with("_") && filename.ends_with(".png") {
				let file_path = base_folder.join(filename.to_string());
				Some(
					ProjectSettings::singleton()
						.globalize_path(&file_path.display().godot())
						.to_string(),
				)
			} else {
				None
			}
		})
		.collect()
}

fn resolve_animation_library(resource_path: &GString) -> Gd<AnimationLibrary> {
	if FileAccess::file_exists(resource_path) {
		match try_load(resource_path) {
			Ok(library) => {
				godot_print!("[Anim] Found animation library: {}", resource_path);
				library
			}
			Err(e) => {
				godot_error!("[Anim] Invalid animation library: {}; {}", resource_path, e);
				AnimationLibrary::new_gd()
			}
		}
	} else {
		AnimationLibrary::new_gd()
	}
}

fn resolve_folder_path(base: &Path, relative_path: &str) -> PathBuf {
	let mut base = base.to_path_buf();
	base.pop();
	base.join(relative_path)
}

fn resolve_atlas_path(base: &Path) -> PathBuf {
	let filename = match base.file_stem().and_then(|stem| stem.to_str()) {
		Some(stem) => format!("{}_atlas.png", stem),
		None => String::from("animation_atlas.png"),
	};

	let mut base = base.to_path_buf();
	base.pop();
	base.push(filename);
	base
}