var TYTDResources = [
    {path="/beer.min.css",value=embed("beer.min.css")},
    {path="/beer.min.js",value=embed("beer.min.js")},
    {path="/material-symbols-outlined.woff2",value=embed("material-symbols-outlined.woff2")},
    {path="/material-symbols-rounded.woff2",value=embed("material-symbols-rounded.woff2")},
    {path="/material-symbols-sharp.woff2",value=embed("material-symbols-sharp.woff2")},
    {path="/material-symbols-subset.woff2",value=embed("material-symbols-subset.woff2")},
    {path="/htmx.min.js",value=embed("htmx.min.js")},
    {path="/favicon.ico",value=embed("favicon.ico")},
    {path="/tytd.svg",value=embed("tytd.svg")},
    {path="/loading-indicator.svg",value=embed("loading-indicator.svg")},
    {path="/theme.css",value=embed("theme.css")},
    {path="/video.min.js",value=embed("video.min.js")},
    {path="/video-js.css",value=embed("video-js.css")},
    {path="/wavy.svg",value=embed("wavy.svg")}
];
var times=1;

class TYTDApp {
    private OOBE_STATE = {
        tag = "UnknownPC",
        pollHours = 3,
        pollMinutes = 0,
        pollSeconds = 0,
        enablePlugins = true
    };

    private TYTD;
    
    public TYTDApp()
    {
        
        var tytdfs = new SubdirFilesystem(FS.Local, GetTYTDDir());
        this.TYTD = new TYTD.Downloader(tytdfs,FS.MakeFull(GetTYTDDir()));
        this.TYTD.Start();
        
    }
   
    public Handle(ctx)
    {
        if(ctx.Path == "/api/v1/login")
        {
            if(ctx.Method == "POST")
            {
                const req=ctx.ReadJson();
                const result = this.TYTD.Login(req.username, req.password);
                if(result)
                {
                    ctx.SendJson({
                        success = true,
                        token = result
                    });
                }
                else {
                    ctx.SendJson({
                        success=false
                    });
                }
                return true;
            }
            else {
                ctx.StatusCode=307;
                ctx.SendRedirect("/login");
                return true;
            }
        }
        if(ctx.Path == "/login")
        {
            var incorrect=false;
            const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/";
            const username = ctx.QueryParams.TryGetFirst("username") ?? "";
            const password = ctx.QueryParams.TryGetFirst("password") ?? "";

            if(ctx.Method == "POST")
            {
                const result = this.TYTD.Login(username, password);
                if(result)
                {
                    ctx.StatusCode = 303;
                    ctx.WithHeader("Set-Cookie",$"Session={result}; SameSite=Strict").SendRedirect("/");
                    return true;
                }
                else incorrect=true;
            }

            ctx.WithMimeType("text/html").SendText(Pages.Login(redirect, incorrect));
            return true;
        }
        const loggedIn = this.TYTD.IsLoggedIn(ctx);
        if(!loggedIn)
        {
            each(var file : TYTDResources)
            {
                if(ctx.Path == file.path)
                {
                    ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
                    return true;
                }
            }
            ctx.StatusCode = 307;
            ctx.SendRedirect($"/login?redirect={Net.Http.UrlEncode(ctx.Path)}");
            return true;
        }
        

        
        

        if(this.TYTD.Config.OobeState == "oobe")
        {
            each(var file : TYTDResources)
            {
                if(ctx.Path == file.path)
                {
                    ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
                    return true;
                }
            }

            if(ctx.Path == "/oobe")
            {
                if(ctx.Method == "POST")
                {
                    const from = ctx.QueryParams.TryGetFirstInt("from");
                    switch(from)
                    {
                        case 1:
                        {
                            const enablePlugins = ctx.QueryParams.GetFirstBoolean("enablePlugins");
                            const tag = ctx.QueryParams.TryGetFirst("tag") ?? "UnknownPC";
                            const pollHours = ctx.QueryParams.TryGetFirstInt("pollHours") ?? 3;
                            const pollMinutes = ctx.QueryParams.TryGetFirstInt("pollMinutes") ?? 0;
                            const pollSeconds = ctx.QueryParams.TryGetFirstInt("pollSeconds") ?? 0;
                            

                            this.OOBE_STATE.tag = tag;
                            this.OOBE_STATE.pollHours = pollHours;
                            this.OOBE_STATE.pollMinutes = pollMinutes;
                            
                            this.OOBE_STATE.pollSeconds = pollSeconds;

                            this.OOBE_STATE.enablePlugins = enablePlugins;

                            const page = this.OOBE_STATE.enablePlugins ? "/oobe?page=2" : "/oobe?page=3";
                            ctx.StatusCode = 303;
                            ctx.SendRedirect(page);
                            
                            return true;
                        }
                            break;
                        case 3:
                        {
                            
                            const createUser = ctx.QueryParams.GetFirstBoolean("user");

                            var seconds = this.OOBE_STATE.pollSeconds;
                            seconds += this.OOBE_STATE.pollMinutes * 60;
                            seconds += this.OOBE_STATE.pollHours * 3600;
                            this.TYTD.Config.TYTDTag = this.OOBE_STATE.tag;
                            this.TYTD.Config.BellTimer = seconds;
                            this.TYTD.Config.EnablePlugins = this.OOBE_STATE.enablePlugins;
                            this.TYTD.Config.OobeState = "welcome";
                            this.TYTD.SaveConfig();


                            if(createUser)
                            {

                                ctx.StatusCode = 303;
                                ctx.SendRedirect("/newuser");

                                return true;
                            } else {
                                
                                ctx.StatusCode = 303;
                                ctx.SendRedirect("/");
                
                                return true;
                            }
                        }
                            break;
                    }
                    ctx.WithMimeType("text/html").SendText(Components.ShellSimple("First Setup", <h1>Malformed data</h1>));
                    return true;   
                }
                const page = ctx.QueryParams.TryGetFirstInt("page");
                switch(page)
                {
                    case 1:
                        ctx.WithMimeType("text/html").SendText(Pages.OobePage1(this.OOBE_STATE));
                        break;
                    case 2:
                        ctx.WithMimeType("text/html").SendText(Pages.OobePage2(this.TYTD,ctx));
                        break;
                    case 3:
                        ctx.WithMimeType("text/html").SendText(Pages.OobePage3());
                        break;
                    default:
                        ctx.WithMimeType("text/html").SendText(Components.ShellSimple("First Setup", <h1>Setup page not found</h1>));
                        break;
                }
                return true;
            }
            if(ctx.Path != "/package-manage" && ctx.Path != "/api/v1/plugin-thumbnail.png")
            {
                ctx.StatusCode = 307;
                ctx.SendRedirect($"/oobe?page=1");
                return true;
            }
        }

        if(ctx.Path == "/welcome")
        {
            const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/settings";
            if(ctx.QueryParams.GetFirstBoolean("closePopup"))
            {
                if(this.TYTD.Config.OobeState == "welcome")
                {
                    this.TYTD.Config.OobeState = "finished";
                    this.TYTD.SaveConfig();
                }
                ctx.StatusCode = 307;
                ctx.SendRedirect(redirect);
                
                return true;
            }
            ctx.WithMimeType("text/html").SendText(Pages.Welcome(redirect));
            return true;
        }
        if(this.TYTD.Config.OobeState == "welcome")
        {
            each(var file : TYTDResources)
            {
                if(ctx.Path == file.path)
                {
                    ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
                    return true;
                }
            }
            if(ctx.Path != "/newuser")
            {
                
                ctx.StatusCode = 307;
                ctx.SendRedirect($"/welcome?redirect={Net.Http.UrlEncode(ctx.Path)}");
                return true;
            }
        }
        if(ctx.Path == "/")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD));
            return true;
        }
        else if(ctx.Path == "/passwd")
        {
            var incorrect=false;
            
            const password = ctx.QueryParams.TryGetFirst("password") ?? "";
            const newpassword = ctx.QueryParams.TryGetFirst("newpassword") ?? "";

            const confirm = ctx.QueryParams.TryGetFirst("newpassword") ?? "";
            if(ctx.Method == "POST")
            {
                this.TYTD.Passwd();
            }

            ctx.WithMimeType("text/html").SendText(Pages.ChangePassword(redirect, incorrect));
        }
        else if(ctx.Path == "/newuser")
        {
            var error = null;
            if(ctx.Method == "POST")
            {
                const username = ctx.QueryParams.TryGetFirst("username");
                const password = ctx.QueryParams.TryGetFirst("password");
                const confirm = ctx.QueryParams.TryGetFirst("confirm");
                const isAdmin = ctx.QueryParams.GetFirstBoolean("isAdmin");
                const canUsePlugins = ctx.QueryParams.GetFirstBoolean("canUsePlugins");
                const canManagePlugins = ctx.QueryParams.GetFirstBoolean("canManagePlugins");
                const canDownloadDB = ctx.QueryParams.GetFirstBoolean("canDownloadDB");
                const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/";
                var flags = 0;
                if(isAdmin) flags |= UserFlags.AdminFlag;
                if(canUsePlugins) flags |= UserFlags.PluginFlag;
                if(canManagePlugins) flags |= UserFlags.ManagePluginFlag;
                if(canDownloadDB) flags |= UserFlags.DatabaseFlag;
                
                
                if(password != confirm)
                {
                    error = "Passwords do not match";
                }
                else if(!TypeIsString(password)) {
                    error = "Password is not defined";

                }
                else if(!TypeIsString(username)) {
                    error = "Username not defined";
                }
                else {
                    error = this.TYTD.CreateAccount(ctx, username, password, flags);
                }

                if(!TypeIsString(error))
                {
                    ctx.StatusCode=303;
                    ctx.SendRedirect(redirect);
                }
            }
            ctx.WithMimeType("text/html").SendText(Pages.NewUser(error));
            return true;
        }
        else if(ctx.Path == "/logout")
        {
            this.TYTD.Logout(ctx);
            ctx.StatusCode = 307;
            ctx.SendRedirect("/");
            return true;
        }
        else if(ctx.Path == "/api")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Api());
            return true;
        }
        else if(ctx.Path == "/api-v1")
        {
            ctx.WithMimeType("text/html").SendText(Pages.ApiV1());
            return true;
        }
        else if(ctx.Path == "/embed")
        {
            var v = ctx.QueryParams.TryGetFirst("v");
            if(TypeIsString(v))
            {
                const video = this.TYTD.GetVideo(v);
                if(TypeIsDefined(video))
                {
                    ctx.WithMimeType("text/html").SendText(Pages.VideoEmbed(v, video.title));
                    return true;
                }
            }
            return false;
        }
        else if(ctx.Path == "/api/v1/download")
        {
            var v = ctx.QueryParams.TryGetFirst("v");
            var inline = ctx.QueryParams.GetFirstBoolean("inline");
            var res = ctx.QueryParams.TryGetFirst("res");

            if(TypeOf(res) != "String") res = Resolution.LowVideo;

            var path = this.TYTD.GetVideoPath(v,res);
            
            if(path != null && this.TYTD.Storage.FileExists(path))
            {
                var info = this.TYTD.GetVideo(v);
                var filename = $"{info.title}-{info.videoId}-{res}{path.GetExtension()}";
                var strm = this.TYTD.Storage.OpenFile(path,"rb");
                ctx.WithMimeType(Net.Http.MimeType(path.ToString())).WithContentDisposition(filename,inline).SendStream(strm);
                strm.Close();
                return true;
            }
            return false;
        }
        else if(ctx.Path == "/api/v1/progress.json")
        {
            var jo = {
                CurrentVideoProgress = this.TYTD.CurrentVideoProgress,
                CurrentVideo = this.TYTD.CurrentVideo
                
            };
            ctx.WithMimeType("application/json").SendJson(jo);
            return true;
        }
        else if(ctx.Path == "/api/v1/video.json")
        {
            var id = ctx.QueryParams.TryGetFirst("v");
            if(TypeOf(id)!="String") id = "";
            ctx.WithMimeType("application/json").SendJson(this.TYTD.GetVideo(id));
            return true;
        }
        else if(ctx.Path == "/api/v1/playlist.json")
        {
            var id = ctx.QueryParams.TryGetFirst("id");
            var page = ctx.QueryParams.TryGetFirstInt("page");
            var count = ctx.QueryParams.TryGetFirstInt("count");
            if(TypeOf(page)!="Long") page = 1;
            if(TypeOf(count)!="Long") count = 20;
            if(TypeOf(id)!="String") id = "";
            ctx.WithMimeType("application/json").SendJson(this.TYTD.GetPlaylistContents(id,page-1,count));
            return true;
        }
        else if(ctx.Path == "/api/v1/channel.json")
        {
            var id = ctx.QueryParams.TryGetFirst("id");
            var page = ctx.QueryParams.TryGetFirstInt("page");
            var count = ctx.QueryParams.TryGetFirstInt("count");
            if(TypeOf(page)!="Long") page = 1;
            if(TypeOf(count)!="Long") count = 20;
            if(TypeOf(id)!="String") id = "";
            ctx.WithMimeType("application/json").SendJson(this.TYTD.GetChannelContents(id,page-1,count));
            return true;
        }
        else if(ctx.Path == "/api/v1/downloads.json")
        {
            var q = ctx.QueryParams.TryGetFirst("q");
            var type = ctx.QueryParams.TryGetFirst("type");
            var page = ctx.QueryParams.TryGetFirstInt("page");
            var count = ctx.QueryParams.TryGetFirstInt("count");
            if(TypeOf(page)!="Long") page = 1;
            if(TypeOf(count)!="Long") count = 20;
            if(TypeOf(type)!="String") type = "videos";
            if(TypeOf(q)!="String") q = "";
           
            var result = { entries = [] };
            switch(type)
            {
                case "videos":
                    
                    result.entries = this.TYTD.GetVideos(q,page-1,count);
                    
                    break;
                case "playlists":
                    result.entries = this.TYTD.GetPlaylists(q,page-1,count);
                    break;
                case "channels":
                    result.entries = this.TYTD.GetChannels(q,page-1,count);
                    break;
                
            }
            ctx.WithMimeType("application/json").SendJson(result);
            return true;
        }
        else if(ctx.Path == "/api/v1/database.db")
        {
            
            if(!this.TYTD.SendDatabase(ctx))
            {
                ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized","<h1>You are not authorized to download the database</h1>",3,/"api/v1/database.db"));
            }
            return true;
        }
        else if(ctx.Path == "/api/v1/personal")
        {
            /*
                GET /api/v1/personal
                GET /api/v1/personal?name=NAME
                DELETE /api/v1/personal
                name=NAME
                id=THEID //If this does not exist delete the entire list
                POST /api/v1/personal
                id=THEID or description=
                name=
            */

            if(ctx.Method == "GET")
            {
                var name = ctx.QueryParams.TryGetFirst("name");
                if(TypeOf(name) == "String")
                {
                    var page = ctx.QueryParams.TryGetFirstInt("page") ?? 1;
                    var count = ctx.QueryParams.TryGetFirstInt("count") ?? 20;

                    var json = {
                        description = this.TYTD.GetPersonalListDescription(name),
                        items = this.TYTD.GetPersonalListContents(name,page-1,count)
                    };
                    ctx.WithMimeType("application/json").SendJson(json);
                    return true;
                }
                else {
                    var json = {
                        items = this.TYTD.GetPersonalLists()
                    };
                    ctx.WithMimeType("application/json").SendJson(json);
                    return true;
                }
            }
            else if(ctx.Method == "POST")
            {
                //{name="",id="",description=""}

                var j = ctx.ReadJson();
                if(TypeOf(j.name) == "String")
                {
                    var idHas = TypeOf(j.id) == "String" && j.id != "";
                    var descHas = TypeOf(j.description) == "String" && j.description != "";
                    if(idHas && descHas)
                    {
                        ctx.WithMimeType("application/json").SendJson({success=false,reason="You provided both a description (to set the list description) and id (to add a item) (you can only provide one)"});
                    }
                    else if(idHas)
                    {
                        this.TYTD.AddToPersonalList(j.name, j.id);
                        ctx.WithMimeType("application/json").SendJson({success=true});
                    }
                    else if(descHas)
                    {
                        this.TYTD.SetPersonalListDescription(j.name,j.description);
                        ctx.WithMimeType("application/json").SendJson({success=true});
                    }
                    else {
                        ctx.WithMimeType("application/json").SendJson({success=false,reason="You must provide either a description (to set the list description) or id (to add a item)"});
                    }
                }
                else {
                    ctx.WithMimeType("application/json").SendJson({success=false, reason="Need a name"});
                }
                
                return true;
            }
            else if(ctx.Method == "DELETE")
            {
                var j = ctx.ReadJson();
                /*
                    public RemoveFromPersonalList(name,id)
	                public RemovePersonalList(name)
                */
                if(TypeOf(j.name) == "String")
                {
                    if(TypeOf(j.id) == "String" && j.id != "")
                    {
                        this.TYTD.RemoveFromPersonalList(j.name,j.id);
                    }
                    else {
                        this.TYTD.RemovePersonalList(j.name);
                    }
                    ctx.WithMimeType("application/json").SendJson({success=true});
                }
                else {
                    ctx.WithMimeType("application/json").SendJson({success=false, reason="Need a name"});
                }
                return true;
            }
            
        }
        else if(ctx.Path == "/subscribe")
        {
            var id = ctx.QueryParams.TryGetFirst("id");
            var bell = ctx.QueryParams.TryGetFirst("bell");
            var type = ctx.QueryParams.TryGetFirst("type");
            

            switch(type)
            {
                case "subscribe":
                    this.TYTD.SetSubscriptionBell(id, SubscriptionBell.BellLow);
                    
                    break;
                case "unsubscribe":
                    this.TYTD.SetSubscriptionBell(id, null);
                    break;
                case "bell":
                    this.TYTD.SetSubscriptionBell(id, bell);
                    break;
            }
            ctx.WithMimeType("text/html").SendText(Components.Subscribe(this.TYTD,id));
            return true;
        }
        else if(ctx.Path == "/whoami")
        {
            const row = this.TYTD.WhoAmI(ctx);
            ctx.WithMimeType("text/html").SendText(Pages.WhoAmI(row));
            return true;
        }
        else if(ctx.Path == "/edit-personal-description")
        {
            var name = ctx.QueryParams.TryGetFirst("name");
            if(ctx.Method == "GET")
            {
                ctx.WithMimeType("text/html").SendText(Components.PersonalListDescription(this.TYTD,name,true));
                return true;
            }
            else if(ctx.Method == "POST")
            {
                var description =ctx.QueryParams.TryGetFirst("description");
                this.TYTD.SetPersonalListDescription(name,description);
                ctx.WithMimeType("text/html").SendText(Components.PersonalListDescription(this.TYTD,name,false));

                return true;
            }
        }
        else if(ctx.Path == "/downloads")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Downloads(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/list")
        {
            ctx.WithMimeType("text/html").SendText(Pages.List(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/settings")
        {

            ctx.WithMimeType("text/html").SendText(Pages.Settings(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/edituser")
        {
            if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx)))
            {
                ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,3));
                return true;
            }
            const user = ctx.QueryParams.TryGetFirst("user");
           
            if(TypeIsString(user))
            {
                var userObj = null;
                const whoami = this.TYTD.WhoAmI(ctx);
                if(ctx.Method == "POST")
                {
                    if(whoami.username != user)
                    {
                        const isAdmin = ctx.QueryParams.GetFirstBoolean("isAdmin");
                        const canUsePlugins = ctx.QueryParams.GetFirstBoolean("canUsePlugins");
                        const canManagePlugins = ctx.QueryParams.GetFirstBoolean("canManagePlugins");
                        const canDownloadDB = ctx.QueryParams.GetFirstBoolean("canDownloadDB");
                        var flags = 0;
                        if(isAdmin) flags |= UserFlags.AdminFlag;
                        if(canUsePlugins) flags |= UserFlags.PluginFlag;
                        if(canManagePlugins) flags |= UserFlags.ManagePluginFlag;
                        if(canDownloadDB) flags |= UserFlags.DatabaseFlag;


                        this.TYTD.Mutex.Lock();
                        const db = this.TYTD.OpenDB();
                        Sqlite.Exec(db, $"UPDATE users SET flags = {flags} WHERE username = {Sqlite.Escape(user)}");
                        Sqlite.Close(db);
                        this.TYTD.Mutex.Unlock();
                    }
                }
                if(ctx.Method == "DELETE")
                {
                    if(whoami.username != user)
                    {
                        this.TYTD.Mutex.Lock();
                        const db = this.TYTD.OpenDB();
                        Sqlite.Exec(db,$"DELETE FROM users WHERE username = {Sqlite.Escape(user)}");
                        Sqlite.Close(db);
                        this.TYTD.Mutex.Unlock();
                        ctx.StatusCode = 200;
                        ctx.WithHeader("HX-Location", "/edituser");
                        ctx.SendText("Redirect");
                    }
                    else
                    {
                        ctx.StatusCode = 401;
                        ctx.WriteHeaders();
                    }
                    return true;
                }



                if(whoami.username != user)
                {
                    this.TYTD.Mutex.Lock();
                    const db = this.TYTD.OpenDB();
                    userObj = Sqlite.Exec(db,$"SELECT * FROM users WHERE username = {Sqlite.Escape(user)}");
                    Sqlite.Close(db);
                    this.TYTD.Mutex.Unlock();
                    if(TypeIsList(userObj) && userObj.Length == 1) userObj = userObj[0];
                    else userObj=null;
                }

                
                
                ctx.WithMimeType("text/html").SendText(Pages.EditUser(userObj));
            }
            else {
                ctx.WithMimeType("text/html").SendText(Pages.EditUserList(this.TYTD, ctx));
            }
            return true;
        }
        else if(ctx.Path == "/admin")
        {
            if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx)))
            {
                ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,3));
                return true;
            }
            if(ctx.Method == "POST")
            {
                
                /*
                    hours
                    minutes
                    seconds
                    tag
                    enablePlugins
                */
                var enablePlugins = ctx.QueryParams.GetFirstBoolean("enablePlugins");
                var tag = ctx.QueryParams.TryGetFirst("tag");
                var hours = ctx.QueryParams.TryGetFirstInt("hours");
                var minutes = ctx.QueryParams.TryGetFirstInt("minutes");
                var seconds = ctx.QueryParams.TryGetFirstInt("seconds");

                if(TypeOf(tag) == "String" && TypeOf(hours) == "Long" && TypeOf(minutes) == "Long" && TypeOf(seconds) == "Long")
                {
                    seconds += minutes * 60;
                    seconds += hours * 3600;
                    this.TYTD.Config.TYTDTag = tag;
                    this.TYTD.Config.BellTimer = seconds;
                    this.TYTD.Config.EnablePlugins = enablePlugins;
                    this.TYTD.SaveConfig();
                }
            }
            ctx.WithMimeType("text/html").SendText(Pages.Admin(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/watch" || ctx.Path == "/video")
        {
            ctx.WithMimeType("text/html").SendText(Pages.VideoInfo(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/playlist")
        {
            ctx.WithMimeType("text/html").SendText(Pages.PlaylistInfo(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/channel")
        {
            ctx.WithMimeType("text/html").SendText(Pages.ChannelInfo(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/add")
        {
            var url = ctx.QueryParams.TryGetFirst("url");
            var action =ctx.QueryParams.TryGetFirst("action");
            var res = ctx.QueryParams.TryGetFirst("res");
            if(action == "add")
            {
                this.TYTD.DownloadItem(url,res);
            }
            else if(action == "info")
            {
                
                ctx.ResponseHeaders.SetValue("HX-Redirect", TYTD.PageRedirect(url));
                
                
                
            }
            else if(action == "download")
            {
                ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}");
               
            }
            else if(action == "play")
            {
                ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}&inline=true");
                
            }
            ctx.WithMimeType("text/html").SendText(Components.Add());
            return true;
        }
        else if(ctx.Path == "/add-video")
        {
            var url = ctx.QueryParams.TryGetFirst("url");
            var action =ctx.QueryParams.TryGetFirst("action");
            var res = ctx.QueryParams.TryGetFirst("res");
            if(action == "add")
            {
                this.TYTD.DownloadItem(url,res);
            }
           
            else if(action == "download")
            {
                ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}");
               
            }
            else if(action == "play")
            {
                ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}&inline=true");
                
            }
            ctx.WithMimeType("text/html").SendText(Components.AddVideo(url));
            return true;
        }
        else if(ctx.Path == "/add-to-list")
        {
            var id = ctx.QueryParams.TryGetFirst("id");
             if(TypeOf(id) != "String") id = "";

            var name = ctx.QueryParams.TryGetFirst("name");
            if(TypeOf(name) != "String") name = "";

            if(name == "")
            {
                ctx.WithMimeType("text/html").SendText(Components.CreateNewList(this.TYTD,id));
            }
            else {
                this.TYTD.AddToPersonalList(name,id);
                ctx.WithMimeType("text/html").SendText(Components.AddToPersonalList(this.TYTD,id));
            }

            
            return true;
        }
        else if(ctx.Path == "/progress")
        {
            ctx.WithMimeType("text/html").SendText(Components.Progress(this.TYTD));
            return true;
        }
        else if(ctx.Path == "/plugins")
        {
            ctx.WithMimeType("text/html").SendText(Pages.Plugins(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/plugins-download")
        {
            if(!UserFlags.CanManagePlugins(this.TYTD.IsLoggedIn(ctx))) return false;
            ctx.WithMimeType("text/html").SendText(Pages.DownloadPlugins(this.TYTD,ctx));
            return true;
        }
        else if(ctx.Path == "/package-manage" && ctx.Method == "POST")
        {
            if(!UserFlags.CanManagePlugins(this.TYTD.IsLoggedIn(ctx))) return false;
            var data = {
                name = ctx.QueryParams.TryGetFirst("name"),
                version = ctx.QueryParams.TryGetFirst("version"),
                action = ctx.QueryParams.TryGetFirst("action")
            };

            var mustConfirm = false;
            

            switch(data.action)
            {
                case "install":
                {
                    var v = Version.Parse(data.version);
                    if(v != null)
                    this.TYTD.PackageInstall(data.name,v);
                }
                    break;
                case "uninstall":
                {
                    mustConfirm = true;
                }
                    break;
                case "confirm":
                {
                    this.TYTD.PackageUninstall(data.name);
                }
                    break;
            }
            ctx.WithMimeType("text/html").SendText(Components.InstallButton(this.TYTD, data.name, data.version, mustConfirm));
            return true;
        }   
        else if(ctx.Path == "/api/v1/video-thumbnail")
        {
            var v = ctx.QueryParams.TryGetFirst("v");
            var res = ctx.QueryParams.TryGetFirst("res");
            if(TypeOf(res) != "String") res = "0";
            var thu = this.TYTD.GetVideoThumbnail(v,res);
            if(thu == null) return false;
            ctx.WithMimeType("image/jpg").SendBytes(thu);
            return true;
        }
        else if(ctx.Path == "/api/v1/playlist-thumbnail")
        {
            var id = ctx.QueryParams.TryGetFirst("id");
            var res = ctx.QueryParams.TryGetFirst("res");
            if(TypeOf(res) != "String") res = "0";
            var thu = this.TYTD.GetPlaylistThumbnail(id,res);
            if(thu == null) return false;
            ctx.WithMimeType("image/jpg").SendBytes(thu);
            return true;
        }
        else if(ctx.Path == "/api/v1/channel-thumbnail")
        {
            var id = ctx.QueryParams.TryGetFirst("id");
            var res = ctx.QueryParams.TryGetFirst("res");
            if(TypeOf(res) != "String") res = "0";
            var thu = this.TYTD.GetChannelThumbnail(id,res);
            if(thu == null) return false;
            ctx.WithMimeType("image/jpg").SendBytes(thu);
            return true;
        }
        else if(ctx.Path == "/api/v1/plugin-thumbnail.png")
        {
            var name = ctx.QueryParams.TryGetFirst("name");
            ctx.WithMimeType().SendBytes(this.TYTD.GetPluginThumbnail(name));
            return true;
        }
        else if(ctx.Path.StartsWith("/plugin/"))
        {
            if(UserFlags.CanUsePlugins(this.TYTD.IsLoggedIn(ctx)))
            return this.TYTD.Servers.Handle(ctx);
            else
            {
                return false;
            }
        }
        else {
            each(var file : TYTDResources)
            {
                if(ctx.Path == file.path)
                {
                    ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
                    return true;
                }
            }
        }
        return false;
    }

    public Close()
    {
        this.TYTD.Stop();
    }
}

func GetTYTDDir()
{
    const dir = Env["TYTDDIR"];
    if(TypeIsString(dir) && dir.Length > 0)
        return dir;
    return (Env.Videos / "TYTD2025").ToString();
}

func WebAppMain(args)
{
    if(args.Length == 2 && args[1] == "change-password")
    {
        Console.Write("Username: ");
        const username = Console.ReadLine();
        Console.Write("Password: ");
        const echo = Console.Echo;
        Console.Echo = false;
        const password = Console.ReadLine();
        Console.WriteLine();
        Console.Write("Confirm: ");
        const confirm = Console.ReadLine();
        Console.WriteLine();
        Console.Echo = true;

        if(password != confirm)
        {
            Console.WriteLine("Passwords do not match!");
            return null;
        }

		const salt = Crypto.RandomBytes(32, "TYTD2025");
	    const hash = Crypto.PBKDF2(password, salt, UserFlags.ITTR,64,384);
        FS.Local.CreateDirectory(GetTYTDDir());
        const db = Sqlite.Open(GetTYTDDir()/"tytd.db");


        const res = Sqlite.Exec(db, $"UPDATE users SET password_hash = {Sqlite.Escape(Crypto.Base64Encode(hash))}, password_salt = {Sqlite.Escape(Crypto.Base64Encode(salt))} WHERE username = {Sqlite.Escape(username)};");
        if(TypeIsList(res))
        {
            Console.WriteLine("Changed password successfully");
        }
        else {
            Console.WriteLine($"Failed to change password: {res}");
        }

        return null;
    }
    return new TYTDApp();
}
func main(args)
{
    var res = WebAppMain(args);
    Net.Http.ListenSimpleWithLoop(res,3255);
    res.Close();
    return 0;
}