// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

use ninja_gen::{
    action::BuildAction,
    command::RunCommand,
    copy::CopyFiles,
    glob, hashmap, inputs,
    node::{CompileSass, EsbuildScript, TypescriptCheck},
    python::{python_format, PythonTest},
    Build, Result, Utf8Path, Utf8PathBuf,
};

use crate::{
    anki_version,
    python::BuildWheel,
    web::{copy_mathjax, eslint},
};

pub fn build_and_check_aqt(build: &mut Build) -> Result<()> {
    build_forms(build)?;
    build_generated_sources(build)?;
    build_data_folder(build)?;
    build_macos_helper(build)?;
    build_wheel(build)?;
    check_python(build)?;
    Ok(())
}

fn build_forms(build: &mut Build) -> Result<()> {
    let ui_files = glob!["qt/aqt/forms/*.ui"];
    let outdir = Utf8PathBuf::from("qt/_aqt/forms");
    let mut py_files = vec![];
    for path in ui_files.resolve() {
        let outpath = outdir.join(path.file_name().unwrap()).into_string();
        py_files.push(outpath.replace(".ui", "_qt5.py"));
        py_files.push(outpath.replace(".ui", "_qt6.py"));
    }
    build.add(
        "qt/aqt:forms",
        RunCommand {
            command: ":pyenv:bin",
            args: "$script $first_form",
            inputs: hashmap! {
                "script" => inputs!["qt/tools/build_ui.py"],
                "" => inputs![ui_files],
            },
            outputs: hashmap! {
                "first_form" => vec![py_files[0].as_str()],
                "" => py_files.iter().skip(1).map(|s| s.as_str()).collect(),
            },
        },
    )
}

/// For legacy reasons, we can not easily separate sources and generated files
/// up with a PEP420 namespace, as aqt/__init__.py exports a bunch of things.
/// To allow code to run/typecheck without having to merge source and generated
/// files into a separate folder, the generated files are exported as a separate
/// _aqt module.
fn build_generated_sources(build: &mut Build) -> Result<()> {
    build.add(
        "qt/aqt:hooks.py",
        RunCommand {
            command: ":pyenv:bin",
            args: "$script $out",
            inputs: hashmap! {
                "script" => inputs!["qt/tools/genhooks_gui.py"],
                "" => inputs!["pylib/anki/_vendor/stringcase.py", "pylib/tools/hookslib.py"]
            },
            outputs: hashmap! {
                "out" => vec!["qt/_aqt/hooks.py"]
            },
        },
    )?;
    build.add(
        "qt/aqt:sass_vars",
        RunCommand {
            command: ":pyenv:bin",
            args: "$script $root_scss $out",
            inputs: hashmap! {
                "script" => inputs!["qt/tools/extract_sass_vars.py"],
                "root_scss" => inputs![":css:_root-vars"],
            },
            outputs: hashmap! {
                "out" => vec![
                    "qt/_aqt/colors.py",
                    "qt/_aqt/props.py"
                ]
            },
        },
    )?;
    Ok(())
}

fn build_data_folder(build: &mut Build) -> Result<()> {
    build_css(build)?;
    build_imgs(build)?;
    build_js(build)?;
    build_pages(build)?;
    build_icons(build)?;
    Ok(())
}

fn build_css(build: &mut Build) -> Result<()> {
    let scss_files = build.expand_inputs(inputs![glob!["qt/aqt/data/web/css/*.scss"]]);
    let out_dir = Utf8Path::new("qt/_aqt/data/web/css");
    for scss in scss_files {
        let stem = Utf8Path::new(&scss).file_stem().unwrap();
        let mut out_path = out_dir.join(stem);
        out_path.set_extension("css");

        build.add(
            "qt/aqt:data/web/css",
            CompileSass {
                input: scss.into(),
                output: out_path.as_str(),
                deps: inputs![":sass"],
                load_paths: vec![".", "node_modules"],
            },
        )?;
    }
    let other_ts_css = build.inputs_with_suffix(
        inputs![
            ":ts:editor",
            ":ts:pages:editable",
            ":ts:reviewer:reviewer.css"
        ],
        ".css",
    );
    build.add(
        "qt/aqt:data/web/css",
        CopyFiles {
            inputs: other_ts_css.into(),
            output_folder: "qt/_aqt/data/web/css",
        },
    )
}

fn build_imgs(build: &mut Build) -> Result<()> {
    build.add(
        "qt/aqt:data/web/imgs",
        CopyFiles {
            inputs: inputs![glob!["qt/aqt/data/web/imgs/*"]],
            output_folder: "qt/_aqt/data/web/imgs",
        },
    )
}

fn build_js(build: &mut Build) -> Result<()> {
    for ts_file in &["deckbrowser", "webview", "toolbar", "reviewer-bottom"] {
        build.add(
            "qt/aqt:data/web/js",
            EsbuildScript {
                script: "ts/transform_ts.mjs".into(),
                entrypoint: format!("qt/aqt/data/web/js/{ts_file}.ts").into(),
                deps: inputs![],
                output_stem: &format!("qt/_aqt/data/web/js/{ts_file}"),
                extra_exts: &[],
            },
        )?;
    }
    let files = inputs![glob!["qt/aqt/data/web/js/*"]];
    eslint(build, "aqt", "qt/aqt/data/web/js", files.clone())?;
    build.add(
        "check:typescript:aqt",
        TypescriptCheck {
            tsconfig: "qt/aqt/data/web/js/tsconfig.json".into(),
            inputs: files,
        },
    )?;
    let files_from_ts = build.inputs_with_suffix(
        inputs![":ts:editor", ":ts:reviewer:reviewer.js", ":ts:mathjax"],
        ".js",
    );
    build.add(
        "qt/aqt:data/web/js",
        CopyFiles {
            inputs: files_from_ts.into(),
            output_folder: "qt/_aqt/data/web/js",
        },
    )?;
    build_vendor_js(build)
}

fn build_vendor_js(build: &mut Build) -> Result<()> {
    build.add("qt/aqt:data/web/js/vendor:mathjax", copy_mathjax())?;
    build.add(
        "qt/aqt:data/web/js/vendor",
        CopyFiles {
            inputs: inputs![
                ":node_modules:jquery",
                ":node_modules:jquery-ui",
                ":node_modules:css-browser-selector",
                ":node_modules:bootstrap-dist"
            ],
            output_folder: "qt/_aqt/data/web/js/vendor",
        },
    )
}

fn build_pages(build: &mut Build) -> Result<()> {
    build.add(
        "qt/aqt:data/web/pages",
        CopyFiles {
            inputs: inputs![":ts:pages"],
            output_folder: "qt/_aqt/data/web/pages",
        },
    )?;
    Ok(())
}

fn build_icons(build: &mut Build) -> Result<()> {
    build_themed_icons(build)?;
    build.add(
        "qt/aqt:data/qt/icons:mdi_unthemed",
        CopyFiles {
            inputs: inputs![":node_modules:mdi_unthemed"],
            output_folder: "qt/_aqt/data/qt/icons",
        },
    )?;
    build.add(
        "qt/aqt:data/qt/icons:from_src",
        CopyFiles {
            inputs: inputs![glob!["qt/aqt/data/qt/icons/*.{png,svg}"]],
            output_folder: "qt/_aqt/data/qt/icons",
        },
    )?;
    build.add(
        "qt/aqt:data/qt/icons",
        RunCommand {
            command: ":pyenv:bin",
            args: "$script $out $in",
            inputs: hashmap! {
                "script" => inputs!["qt/tools/build_qrc.py"],
                "in" => inputs![
                    ":qt/aqt:data/qt/icons:mdi_unthemed",
                    ":qt/aqt:data/qt/icons:mdi_themed",
                    ":qt/aqt:data/qt/icons:from_src",
                ]
            },
            outputs: hashmap! {
                "out" => vec!["qt/_aqt/data/qt/icons.qrc"]
            },
        },
    )?;
    Ok(())
}

fn build_themed_icons(build: &mut Build) -> Result<()> {
    let themed_icons_with_extra = hashmap! {
        "chevron-up" => &["FG_DISABLED"],
        "chevron-down" => &["FG_DISABLED"],
        "drag-vertical" => &["FG_SUBTLE"],
        "drag-horizontal" => &["FG_SUBTLE"],
    };
    for icon_path in build.expand_inputs(inputs![":node_modules:mdi_themed"]) {
        let path = Utf8Path::new(&icon_path);
        let stem = path.file_stem().unwrap();
        let mut colors = vec!["FG"];
        if let Some(&extra) = themed_icons_with_extra.get(stem) {
            colors.extend(extra);
        }
        build.add(
            "qt/aqt:data/qt/icons:mdi_themed",
            BuildThemedIcon {
                src_icon: path,
                colors,
            },
        )?;
    }
    Ok(())
}

struct BuildThemedIcon<'a> {
    src_icon: &'a Utf8Path,
    colors: Vec<&'a str>,
}

impl BuildAction for BuildThemedIcon<'_> {
    fn command(&self) -> &str {
        "$pyenv_bin $script $in $colors $out"
    }

    fn files(&mut self, build: &mut impl ninja_gen::build::FilesHandle) {
        let stem = self.src_icon.file_stem().unwrap();
        // eg foo-light.svg, foo-dark.svg, foo-FG_SUBTLE-light.svg, foo-FG_SUBTLE-dark.svg
        let outputs: Vec<_> = self
            .colors
            .iter()
            .flat_map(|&color| {
                let variant = if color == "FG" {
                    "".into()
                } else {
                    format!("-{color}")
                };
                [
                    format!("qt/_aqt/data/qt/icons/{stem}{variant}-light.svg"),
                    format!("qt/_aqt/data/qt/icons/{stem}{variant}-dark.svg"),
                ]
            })
            .collect();

        build.add_inputs("pyenv_bin", inputs![":pyenv:bin"]);
        build.add_inputs("script", inputs!["qt/tools/color_svg.py"]);
        build.add_inputs("in", inputs![self.src_icon.as_str()]);
        build.add_inputs("", inputs![":qt/aqt:sass_vars"]);
        build.add_variable("colors", self.colors.join(":"));
        build.add_outputs("out", outputs);
    }
}

fn build_macos_helper(build: &mut Build) -> Result<()> {
    if cfg!(target_os = "macos") {
        build.add(
            "qt/aqt:data/lib:libankihelper",
            RunCommand {
                command: ":pyenv:bin",
                args: "$script $out $in",
                inputs: hashmap! {
                    "script" => inputs!["qt/mac/helper_build.py"],
                    "in" => inputs![glob!["qt/mac/*.swift"]],
                },
                outputs: hashmap! {
                    "out" => vec!["qt/_aqt/data/lib/libankihelper.dylib"],
                },
            },
        )?;
    }
    Ok(())
}

fn build_wheel(build: &mut Build) -> Result<()> {
    build.add(
        "wheels:aqt",
        BuildWheel {
            name: "aqt",
            version: anki_version(),
            src_folder: "qt/aqt",
            gen_folder: "$builddir/qt/_aqt",
            platform: None,
            deps: inputs![":qt/aqt", glob!("qt/aqt/**"), "python/requirements.aqt.in"],
        },
    )
}

fn check_python(build: &mut Build) -> Result<()> {
    python_format(
        build,
        "qt",
        inputs![glob!("qt/**/*.py", "qt/bundle/PyOxidizer/**")],
    )?;

    build.add(
        "check:pytest:aqt",
        PythonTest {
            folder: "qt/tests",
            python_path: &["pylib", "$builddir/pylib", "$builddir/qt"],
            deps: inputs![":pylib/anki", ":qt/aqt", glob!["qt/tests/**"]],
        },
    )
}
