import HtmlWebpackPlugin from "html-webpack-plugin";
import HtmlInlineScriptPlugin from 'html-inline-script-webpack-plugin';
import FaviconsWebpackPlugin from "favicons-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";
import WorkboxPlugin from "workbox-webpack-plugin";
import ReactRefreshTypeScript from 'react-refresh-typescript';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import webpack from "webpack";
import { execSync } from "child_process";
import moment from "moment";
import path from "path";
import fetch from "node-fetch";
import { fileURLToPath } from 'url';
import * as sass from "sass";

function parseChangelog() {
    return execSync("git log --grep \"^Changelog: \" -10")
        .toString()
        .split(/^commit /m)
        .slice(1)
        .map((commit) => {
            const changelogIndex = commit.indexOf("    Changelog: ");
            if (changelogIndex === -1) {
                throw `Changelog not found in commit:\n${commit}`;
            }
            const dateString = commit.match(/^Date:   (.*)$/m)?.[1];
            if (!dateString) {
                throw `Date not found in commit:\n${commit}`;
            }
            const date = moment(new Date(dateString)).utc().format("YYYY-MM-DD");
            const id = commit.substring(0, commit.indexOf("\n"));
            const message = commit
                .substring(changelogIndex + 15)
                .replaceAll("\n    ", "\n")
                .trim();
            return {
                id,
                message,
                date,
            };
        })
        .filter((changelog) => changelog.message);
}

export default async (env, argv) => {
    const getContributorsForRepo = async (repoName) => {
        const contributorsData = await fetch(`https://api.github.com/repos/LiveSplit/${repoName}/contributors`, {
            headers: {
                "Authorization": env.GITHUB_TOKEN ? `Bearer ${env.GITHUB_TOKEN}` : undefined,
            },
        });
        return contributorsData.json();
    }

    const lsoContributorsList = await getContributorsForRepo("LiveSplitOne");
    const coreContributorsList = await getContributorsForRepo("livesplit-core");

    const coreContributorsMap = {};
    for (const coreContributor of coreContributorsList) {
        if (coreContributor.type === "User" && !coreContributor.login.includes("dependabot")) {
            coreContributorsMap[coreContributor.login] = coreContributor;
        }
    }

    for (let lsoContributor of lsoContributorsList) {
        const existingContributor = coreContributorsMap[lsoContributor.login];
        if (existingContributor) {
            existingContributor.contributions += lsoContributor.contributions;
        } else if (lsoContributor.type === "User" && !lsoContributor.login.includes("dependabot")) {
            coreContributorsMap[lsoContributor.login] = lsoContributor;
        }
    }

    const contributorsList = Object.values(coreContributorsMap)
        // Sort by contributions, but fallback to alphabetical order for the
        // same amount of contributions
        .sort((a, b) => a.login > b.login ? 1 : b.login > a.login ? -1 : 0)
        .sort((a, b) => b.contributions - a.contributions)
        .map((user) => {
            return { id: user.id, name: user.login };
        });
    const commitHash = execSync("git rev-parse --short HEAD").toString();
    const date = moment.utc().format("YYYY-MM-DD kk:mm:ss z");

    const changelog = parseChangelog();

    const basePath = path.dirname(fileURLToPath(import.meta.url));

    const isProduction = argv.mode === "production";
    const isTauri = env.TAURI === "true";
    const distPath = path.join(...[
        basePath,
        ...(isTauri ? ["src-tauri", "target", "dist"] : ["dist"]),
    ]);

    return {
        entry: {
            "bundle": ["./src/index.tsx"],
        },
        output: {
            filename: "[name].js",
            path: distPath,
            publicPath: '',
        },

        devtool: isProduction ? undefined : "source-map",

        devServer: {
            port: 8080,
            hot: true
        },

        resolve: {
            extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js", ".json", ".wasm"],
        },

        plugins: [
            ...(isProduction ? [new CleanWebpackPlugin({
                protectWebpackAssets: false,
                cleanAfterEveryBuildPatterns: ['*.LICENSE.txt'],
            })] : []),
            ...(isTauri ? [] : [new FaviconsWebpackPlugin({
                logo: path.resolve("src/assets/icon.svg"),
                inject: true,
                logoMaskable: path.resolve("src/assets/maskable.svg"),
                favicons: {
                    appName: "LiveSplit One",
                    appDescription: "A version of LiveSplit that works on a lot of platforms.",
                    developerName: "CryZe",
                    developerURL: "https://livesplit.org",
                    background: "#171717",
                    theme_color: "#232323",
                    appleStatusBarStyle: "black-translucent",
                    icons: {
                        appleIcon: {
                            offset: 10,
                        },
                        appleStartup: {
                            offset: 15,
                        },
                        windows: false,
                        coast: false,
                        yandex: false,
                    },
                    start_url: "/",
                },
            })]),
            new HtmlWebpackPlugin({
                template: "./src/index.html",
            }),
            new webpack.DefinePlugin({
                BUILD_DATE: JSON.stringify(date),
                COMMIT_HASH: JSON.stringify(commitHash),
                CONTRIBUTORS_LIST: JSON.stringify(contributorsList),
                CHANGELOG: JSON.stringify(changelog),
            }),
            ...(isProduction ? [
                new HtmlInlineScriptPlugin({
                    scriptMatchPattern: ['^bundle.js$'],
                }),
                ...(isTauri ? [] : [new WorkboxPlugin.GenerateSW({
                    clientsClaim: true,
                    skipWaiting: true,
                    maximumFileSizeToCacheInBytes: 100 * 1024 * 1024,
                    exclude: [
                        /^assets/,
                        /\.LICENSE\.txt$/,
                    ],

                    runtimeCaching: [{
                        urlPattern: (context) => {
                            return self.origin === context.url.origin &&
                                context.url.pathname.startsWith("/assets/");
                        },
                        handler: "CacheFirst",
                    }],
                })])
            ] : []),
            ...(!isProduction ? [new ReactRefreshWebpackPlugin()] : []),
        ],

        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    use: [
                        {
                            loader: "ts-loader",
                            options: {
                                getCustomTransformers: () => ({
                                    before: [!isProduction && ReactRefreshTypeScript()].filter(Boolean),
                                }),
                                transpileOnly: !isProduction,
                            },
                        },
                    ],
                    exclude: "/node_modules",
                },
                {
                    test: /\.(s?)css$/,
                    use: [
                        "style-loader",
                        {
                            loader: "css-loader",
                            options: {
                                importLoaders: 1,
                                modules: "icss",
                            },
                        },
                        {
                            loader: "sass-loader",
                            options: {
                                // Prefer `dart-sass`
                                implementation: sass,
                            },
                        },
                    ],
                },
                {
                    test: /\.(png|jpg|gif|woff|ico|svg)$/,
                    type: 'asset/resource'
                },
            ],
        },

        experiments: {
            syncWebAssembly: true,
            topLevelAwait: true,
        },

        mode: isProduction ? "production" : "development",
    };
};
