#[doc(inline)]
pub use iced_test::selector;

use crate::Theme;

use iced::{window, Element};
use iced_test::Simulator;

use std::env;
use std::path::PathBuf;
use std::str::FromStr;

/// This env var can be set to one of [`TestKind`] cases.
pub const TEST_KIND_ENV_VAR: &str = "TEST_KIND";

pub const SCREENSHOTS_DIR: &str = "screenshots";
pub const PREVIEW_DIR: &str = "preview";

pub const WINDOW_SIZE: iced::Size = iced::Size {
    width: 1024.0,
    height: 768.0,
};

#[derive(Debug, Default, strum::EnumString)]
#[strum(ascii_case_insensitive)]
pub enum TestKind {
    /// (default): Compare new screenshot against the stored ones. The tests
    /// with any difference will fail
    #[default]
    Compare,
    /// Preview the new screenshots in single resolution without comparing
    /// against the stored ones.
    Preview,
    /// Delete the old screenshots and save the new ones.
    Accept,
}

#[derive(Debug)]
pub struct TestViewResult<'a> {
    pub name: &'a str,
    pub succeeded: bool,
}

#[derive(Debug, Clone, Copy)]
pub enum Size {
    Fullscreen,
    Specific { width: f32, height: f32 },
}

/// The name of the test calling this function MUST start with "view".
pub fn test_view<'a, 'b, Message, Renderer>(
    results: &mut Vec<TestViewResult<'b>>,
    uniq_name: &'b str,
    element: impl Into<Element<'a, Message, Theme, Renderer>>,
    size: Size,
) where
    Renderer: iced::advanced::Renderer + iced::advanced::renderer::Headless,
{
    test_view_change_sim(results, uniq_name, element, size, |_sim| {})
}

/// The name of the test calling this function MUST start with "view".
pub fn test_view_change_sim<'a, 'b, Message, Renderer, F>(
    results: &mut Vec<TestViewResult<'b>>,
    uniq_name: &'b str,
    element: impl Into<Element<'a, Message, Theme, Renderer>>,
    size: Size,
    change_sim: F,
) where
    Renderer: iced::advanced::Renderer + iced::advanced::renderer::Headless,
    F: Fn(&mut Simulator<Message, Theme, Renderer>),
{
    let test_kind = test_kind();

    let size = match size {
        Size::Fullscreen => WINDOW_SIZE,
        Size::Specific { width, height } => iced::Size { width, height },
    };
    let mut sim = Simulator::<_, Theme, _>::with_size(
        iced::Settings::default(),
        size,
        element,
    );
    change_sim(&mut sim);
    let snapshot = sim.snapshot(&theme()).unwrap();

    let file_name = format!("{uniq_name}.png");
    match test_kind {
        TestKind::Compare => {
            let dir = PathBuf::from(PREVIEW_DIR);
            let matched = snapshot.matches_image(dir.join(&file_name)).unwrap();
            // Return result to collect all runs first
            results.push(TestViewResult {
                name: uniq_name,
                succeeded: matched,
            })
        }
        TestKind::Preview => {
            let dir = PathBuf::from(PREVIEW_DIR);
            let matched = snapshot.matches_image(dir.join(&file_name)).unwrap();
            assert!(matched, "The view {uniq_name} doesn't have a unique name");
            // Preview mode doesn't need a result
        }
        TestKind::Accept => {
            let dir = PathBuf::from(SCREENSHOTS_DIR);
            let matched = snapshot.matches_image(dir.join(&file_name)).unwrap();
            assert!(matched, "The view {uniq_name} doesn't have a unique name");
            // Accept mode doesn't need a result
        }
    }
}

pub fn test_kind() -> TestKind {
    if let Ok(val) = env::var(TEST_KIND_ENV_VAR) {
        let val = val.trim();
        if !val.is_empty() {
            return TestKind::from_str(val).unwrap_or_else(|err| {
                panic!("Unexpected test kind: {val}, failed parsing with {err}")
            });
        }
    }
    TestKind::default()
}

pub fn report_results(results: Vec<TestViewResult<'_>>) {
    let failed: Vec<_> = results
        .into_iter()
        .filter_map(
            |TestViewResult { name, succeeded }| {
                if succeeded {
                    None
                } else {
                    Some(name)
                }
            },
        )
        .collect();

    if !failed.is_empty() {
        panic!("Changed views: {failed:#?}");
    }
}

fn theme() -> Theme {
    crate::theme(&(), window::Id::unique())
}