/* eslint eqeqeq: 0, curly: "error", "no-extra-parens": "off"  */
/* global API,UI,PluginHandler,ko,ace */
const self = this;

this.plugin = {
    FMFastModeBasePath: "",

    PreInit: function () {
        //Called prior to the plugins initialisation, before the tabs are loaded.
        //This method must not invoke any module/plugin specific API calls.
    },

    PostInit: function () {
        const tab = document.getElementById("tab_StorePlugin_Store");
        if (tab == null) { return; }
        if (!userHasPermission("Store.Store.BrowseStore")) { return; }
        ko.applyBindings(storeHomeVM, tab);
    },

    AMPDataLoaded: function () {

    },

    Reset: function () {

    }
};

this.tabs = [
    {
        File: "Store.html",
        ExternalTab: false,
        ShortName: "Store",
        Name: "Store (Beta)",
        Icon: "shopping_bag",
        RequiredPermission: "Store.Store.BrowseStore",
        Click: initStore,
        BodyClass: "noPaddingTab",
        Order: 10,
        PopHandler: handlePopstate,
    },
];

this.stylesheet = "Stylesheet.css";    //Styles for tab-specific styles

this.features = {};

const storeHomeVM = new StoreHomeVM();

async function initStore() {
    storeHomeVM.refresh();
};
function StoreHomeVM() {
    const self = this;
    this.Stores = ko.observableArray([]);
    this.SelectedStore = ko.observable(null);
    this.AppName = ko.pureComputed(() => viewModels.support?.appName() || "this game");
    this.refresh = async function () {
        const storeData = await API.StorePlugin.GetAvailableStoresAsync();
        self.Stores.removeAll();
        const storeVMs = ko.quickmap.to(StoreVM, storeData, false, { vm: self });
        ko.utils.arrayPushAll(self.Stores, storeVMs);
    };
    this.storeFunctions = {
        "ampthemesstore": {
            "preview": async function (entryVM) {
                const theme = entryVM.SanitisedName();
                window.open(`${window.location.origin}/ThemePreview.html?theme=${encodeURIComponent(theme)}&t=${Date.now()}`, "_blank");
            },
            "install": async function (entryVM) {
                const theme = entryVM.SanitisedName();
                currentSettings["Core.AMP.Theme"].value(theme);
            },
        },
    };
}

function StoreVM() {
    const self = this;
    this.vm = null; //of StoreHomeVM
    this.Id = ko.observable("");
    this.Name = ko.observable("");
    this.SanitisedName = ko.pureComputed(() => this.Name().toLowerCase().replace(/[^a-z0-9]+/g, '-'));
    this.Description = ko.observable("");
    this.LogoUrl = ko.observable("");
    this.BrandColor1 = ko.observable("");
    this.BrandColor2 = ko.observable("");
    this.BrandColor3 = ko.observable("");
    this.Unavailable = ko.observable(false);
    this.CanInstall = ko.observable(false);
    this.CanUninstall = ko.observable(false);
    this.CanUpdate = ko.observable(false);
    this.CanEnableDisable = ko.observable(false);
    this.CanPreview = ko.observable(false);
    this.BrandGradient = ko.pureComputed(() => this.Unavailable() ? "linear-gradient(to top right, #666, #222)" : `linear-gradient(to top right, ${this.BrandColor1()}, ${this.BrandColor2()})`);
    this.Categories = ko.observableArray([]);
    this.SelectedCategory = ko.observable(null); //of StoreCategoryVM
    this.Selected = ko.observable(false);
    this.loaded = ko.observable(false);
    this.Filters = ko.observableArray([]);
    this.FiltersLoaded = ko.observable(false);
    this.loadingFilters = false;
    this.filterSaveTimer = null;

    this.searchVM = new StoreCategoryVM();
    this.searchVM.store = this;
    this.searchVM.IsSearch = true;
    this.searchVM.IconName = "search";
    this.searchVM.Name = ko.pureComputed(() => `Search ${this.Name()}`);

    this.SearchTerm = ko.observable("");

    this.GoBack = function () {
        this.Selected(false);
        this.vm.SelectedStore(null);
        UI.NavigateTo("/store");
    }

    this.Click = async function (noNav) {
        if (this.Unavailable()) { return; }
        this.vm.SelectedStore()?.Selected(false);
        this.vm.SelectedStore(this);
        if (!this.loaded()) {
            await this.refresh();
        }
        await this.loadFilters();
        if (noNav !== true) {
            UI.NavigateTo("/store/" + this.SanitisedName());
        }
    };

    this.refresh = async () => {
        const storeData = await API.StorePlugin.GetStoreCategoriesAsync(this.Id());
        this.Categories.removeAll();
        const categoryVMs = ko.quickmap.to(StoreCategoryVM, storeData, false, { store: this });
        ko.utils.arrayPushAll(this.Categories, categoryVMs);
        this.loaded(true);
    };

    this.loadFilters = async () => {
        this.FiltersLoaded(false);
        this.Filters.removeAll();
        this.loadingFilters = true;

        const keys = await API.StorePlugin.GetStoreFilterKeysAsync(this.Id());
        const savedSelections = await API.StorePlugin.GetStoreFilterSelectionsAsync(this.Id());
        if (!keys || keys.length === 0) {
            this.loadingFilters = false;
            this.FiltersLoaded(true);
            return;
        }
        const filterVMs = [];
        for (const key of keys) {
            const filter = new StoreFilterVM();
            filter.Key(key);
            filter.Label(formatFilterLabel(key));
            const values = await API.StorePlugin.GetStoreFilterValuesAsync(this.Id(), key);
            filter.Options(values);
            if (savedSelections?.hasOwnProperty(key)) {
                filter.SelectedValue(savedSelections[key]);
            }
            filter.SelectedValue.subscribe(() => {
                if (self.loadingFilters) { return; }
                self.queueSaveFilters();
            });
            filterVMs.push(filter);
        }
        ko.utils.arrayPushAll(this.Filters, filterVMs);
        this.loadingFilters = false;
        this.FiltersLoaded(true);
    };

    this.queueSaveFilters = function () {
        if (this.filterSaveTimer) {
            clearTimeout(this.filterSaveTimer);
        }
        this.filterSaveTimer = setTimeout(() => this.saveFilterSelections(), 250);
    };

    this.saveFilterSelections = async function () {
        const selections = {};
        this.Filters().forEach(filter => {
            const value = filter.SelectedValue();
            if (value && value.length > 0) {
                selections[filter.Key()] = value;
            }
        });
        await API.StorePlugin.SetStoreFilterSelectionsAsync(this.Id(), selections);
    };

    this.SearchStore = async function () {
        if (this.SearchTerm().trim().length === 0) { return; }
        this.SelectedCategory()?.Selected(false);
        this.searchVM.Description = ko.observable(`Search results for "${this.SearchTerm()}"`);
        this.searchVM.SearchTerm(this.SearchTerm());
        this.SelectedCategory(this.searchVM);
        await this.searchVM.refresh();
    };
};

function StoreFilterVM() {
    this.Key = ko.observable("");
    this.Label = ko.observable("");
    this.Options = ko.observableArray([]);
    this.SelectedValue = ko.observable("");
}

function formatFilterLabel(value) {
    return value.replace(/([a-z])([A-Z])/g, "$1 $2");
}

function StoreCategoryVM() {
    const self = this;
    this.store = null; //of StoreVM
    this.Id = ko.observable("");
    this.Name = ko.observable("");
    this.SanitisedName = ko.pureComputed(() => this.Name().toLowerCase().replace(/[^a-z0-9]+/g, '-'));
    this.Description = ko.observable("");
    this.Type = ko.observable("");
    this.IconUrl = ko.observable("");
    this.IconName = ko.observable("");
    this.IconSvg = ko.observable("");
    this.Selected = ko.observable(false);
    this.loaded = ko.observable(false);
    this.Entries = ko.observableArray([]); //of StoreEntryVM
    this.CurrentPage = ko.observable(0);
    this.PageCount = ko.observable(0);
    this.PageSize = ko.observable(20);

    this.IsSearch = false;
    this.SearchTerm = ko.observable("");
    this.Busy = false;

    this.MorePagesAvailable = ko.pureComputed(() => this.CurrentPage() < this.PageCount() - 1);

    this.GoBack = function () {
        self.Selected(false);
        self.store.SelectedCategory(null);
    }

    this.Click = async function (noNav) {
        self.store.SelectedCategory()?.Selected(false);
        self.store.SelectedCategory(this);
        if (noNav !== true) {
            UI.NavigateTo(`/store/${self.store.SanitisedName()}/${this.SanitisedName()}`);
        }
        await this.Reset();
    };

    this.Reset = async function () {
        self.CurrentPage(0);
        self.PageCount(0);
        await this.refresh();
    };

    this.refresh = async function () {
        if (this.Busy) { return; }

        this.Busy = true;
        let entries = [];

        if (self.IsSearch) {
            entries = await API.StorePlugin.SearchStoreAsync(self.store.Id(), self.SearchTerm(), self.CurrentPage(), self.PageSize());
        }
        else {
            entries = await API.StorePlugin.GetStoreEntriesForCategoryAsync(this.store.Id(), this.Id(), this.Type(), this.CurrentPage(), this.PageSize());
        }

        if (this.CurrentPage() == 0) {
            this.Entries.removeAll();
        }

        const entryVMs = ko.quickmap.to(StoreEntryVM, entries.Records, false, { category: self });
        ko.utils.arrayPushAll(this.Entries, entryVMs);
        this.PageCount(entries.TotalPages);
        this.CurrentPage(entries.PageIndex);
        this.loaded(true);

        this.Busy = false;

        await new Promise(resolve => setTimeout(resolve, 100));
        this.setupInfiniteScroll();
    };

    this.advancePage = async function () {
        if (self.Busy) { return; }

        if (self.CurrentPage() >= self.PageCount() - 1) {
            return; //Already on last page
        }

        self.CurrentPage(self.CurrentPage() + 1);

        await self.refresh();
    };

    // Use a sentinel element to trigger loading to accommodate high resolution screens
    this.setupInfiniteScroll = function () {
        const observer = new IntersectionObserver(async (entries) => {
            if (entries[0].isIntersecting && self.MorePagesAvailable() && !self.Busy) {
                await self.advancePage();
            }
        }, { rootMargin: '100px' });

        const sentinel = document.querySelector('.entry-list-sentinel');
        if (sentinel) observer.observe(sentinel);
    };
};

function StoreEntryVM() {
    this.category = null; //of StoreCategoryVM
    this.Busy = ko.observable(false);
    this.Id = ko.observable("");
    this.Slug = ko.observable("");
    this.Name = ko.observable("");
    this.SanitisedName = ko.pureComputed(() => this.Name().replace(/[^a-z0-9]+/gi, '-'));
    this.Description = ko.observable("");
    this.Version = ko.observable("");
    this.AuthorName = ko.observable("");
    this.AuthorId = ko.observable("");
    this.Tags = ko.observableArray([]);
    this.Categories = ko.observableArray([]);
    this.Dependencies = ko.observableArray([]);
    this.Provides = ko.observableArray([]);
    this.DownloadCount = ko.observable(0);
    this.DownloadUrl = ko.observable("");
    this.DownloadCountFormatted = ko.computed(() => {
        const count = this.DownloadCount();
        if (count >= 1000000) {
            return `${(count / 1000000).toFixed(1)}M`;
        } else if (count >= 1000) {
            return `${(count / 1000).toFixed(1)}K`;
        }
        return count;
    });
    this.SourceUrl = ko.observable("");
    this.IconUrl = ko.observable("");
    this.DiscussionUrl = ko.observable("");
    this.ScreenshotUrls = ko.observableArray([]);
    this.RatingStars = ko.observable(null);
    this.RatingStarsFormatted = ko.computed(() => {
        const stars = this.RatingStars();
        if (stars === null) {
            return "";
        }
        if (stars < 0 || stars > 5) { return ""; }
        const fullStars = Math.floor(stars);
        const halfStar = stars % 1 >= 0.5 ? "★" : "";
        return "★".repeat(fullStars) + halfStar + "☆".repeat(5 - fullStars - (halfStar ? 1 : 0));
    });
    this.RatingCount = ko.observable(null);
    this.RatingCountFormatted = ko.computed(() => {
        const count = this.RatingCount();
        if (count >= 1000000) {
            return `${(count / 1000000).toFixed(1)}M`;
        } else if (count >= 1000) {
            return `${(count / 1000).toFixed(1)}K`;
        }
        return count;
    });
    this.SizeBytes = ko.observable(0);
    this.SizeBytesFormatted = ko.computed(() => {
        const size = this.SizeBytes();
        if (size === 0) { return ""; }
        if (size >= 1000000000) {
            return `${(size / 1000000000).toFixed(1)} GB`;
        } else if (size >= 1000000) {
            return `${(size / 1000000).toFixed(1)} MB`;
        } else if (size >= 1000) {
            return `${(size / 1000).toFixed(1)} KB`;
        }
        return `< 1 KB`;
    });
    this.IsInstalled = ko.observable(false);
    this.InstalledVersion = ko.observable("");
    this.InstalledPath = ko.observable("");
    this.InstalledDate = ko.observable(null);
    this.Disabled = ko.observable(false);
    this.UpdateAvailable = ko.pureComputed(() => this.IsInstalled() && this.Version() !== this.InstalledVersion());
    this.AutoUpdate = ko.observable(false);
    this.RequiresManualInstall = ko.observable(false);
    this.RequiresPayment = ko.observable(false);
    this.PaymentCurrency = ko.observable("");
    this.PaymentAmount = ko.observable(0.0);
    this.Selected = ko.observable(false);
    this.loaded = ko.observable(false);
    this.Click = async function () {
        if (!this.loaded()) {
            await this.refresh();
        }
    };
    this.refresh = async function () {
        const entryData = await API.StorePlugin.GetStoreEntryAsync(this.category.store.Id(), this.Id());
        ko.quickmap.map(this, entryData);
        this.loaded(true);
    };
    this.LocalInstall = async function () {
        const funcKey = this.category.store.Id().toLowerCase();
        if (!storeHomeVM.storeFunctions.hasOwnProperty(funcKey)) { return false; }
        const funcSet = storeHomeVM.storeFunctions[funcKey];
        if (!funcSet.hasOwnProperty("install")) { return false; }
        await funcSet["install"](this);
        return true;
    };
        this.Install = async function () {
            if (this.IsInstalled()) {
                return;
            }

            plausible("InstallStoreItem", { props: { store: this.category.store.Name(), plugin: this.Name() } });

            const didLocalInstall = await this.LocalInstall();
            if (didLocalInstall) {
                await this.refresh();
                return;
            }

            if (this.RequiresManualInstall()) {
                await UI.ShowModalAsync("Manual Installation Required", "This plugin cannot be installed automatically. Please download this plugin manually and upload it into your instance.", UI.Icons.Exclamation, UI.OKActionOnly);
                return;
            }

            this.Busy(true);
            const installTask = await API.StorePlugin.InstallStoreEntryTaskAsync(this.category.store.Id(), this.Id());
            await installTask.waitCompleteAsync();
            await this.refresh();
            this.Busy(false);
        };
    this.Uninstall = async function () {
        if (!this.IsInstalled()) {
            return;
        }
        this.Busy(true);
        const uninstallResult = await API.StorePlugin.UninstallStoreEntryAsync(this.category.store.Id(), this.Id());
        await this.refresh();
        if (!uninstallResult.Status) {
            await UI.ShowModalAsync("Uninstall Failed", `The plugin could not be uninstalled:\n\n${uninstallResult.Reason}`, UI.Icons.Exclamation, UI.OKActionOnly);
        }
        else {
            this.IsInstalled(false);
        }
        this.Busy(false);
    };
    this.Update = async function () {
        if (!this.IsInstalled()) {
            return;
        }
        this.Busy(true);
        await API.StorePlugin.UpdateStoreEntryAsync(this.category.store.Id(), this.Id());
        await this.refresh();
        this.Busy(false);
    };
    this.ToggleDisabled = async function () {
        if (!this.IsInstalled()) {
            return;
        }
        this.Busy(true);
        await API.StorePlugin.SetStoreEntryDisabledAsync(this.category.store.Id(), this.Id(), !this.Disabled());
        await this.refresh();
        this.Busy(false);
    };
    this.ToggleAutoUpdate = async function () {
        if (!this.IsInstalled()) {
            return;
        }
        this.Busy(true);
        await API.StorePlugin.SetStoreEntryAutoUpdate(this.category.store.Id(), this.Id(), !this.AutoUpdate());
        await this.refresh();
        this.Busy(false);
    };
    this.Preview = async function () {
        const funcKey = this.category.store.Id().toLowerCase();
        if (!storeHomeVM.storeFunctions.hasOwnProperty(funcKey)) { return; }
        const funcSet = storeHomeVM.storeFunctions[funcKey];
        if (!funcSet.hasOwnProperty("preview")) { return; }
        await funcSet["preview"](this);
    };
};

async function handlePopstate(_, segments) {
    if (segments.length < 1 || segments[0] !== "store") { return; }

    if (segments.length == 1) {
        storeHomeVM.SelectedStore(null);
        return;
    }

    const storeName = segments[1];
    const store = storeHomeVM.Stores().find(s => s.SanitisedName() === storeName);
    if (!store) {
        storeHomeVM.SelectedStore()?.Selected(false);
        storeHomeVM.SelectedStore(null);
        return;
    }

    await store.Click(true);
    if (segments.length == 2) {
        store.SelectedCategory(null);
        return;
    }

    const categoryName = segments[2];
    const category = store.Categories().find(c => c.SanitisedName() === categoryName);
    if (category == null) {
        return;
    }

    await category.Click(true);
};

