import { base64ToBytes } from "@components/admin/util";
import { pageRepo } from "@components/repo/page";
import type { ListPage, PageList } from "@components/types/page";
import { get } from "svelte/store";
import yaml from "js-yaml";

export class OpenEditPage {
    public content: string = "";
    public frontmatter: { [key: string]: string | string[] | Date } = $state({});
    public dirty: boolean = $state(false);

    public readonly fileType: string;

    public constructor(
        private manager: PageManager,
        public readonly pageId: number,
        public readonly pageTitle: string,
        public readonly sha: string,
        public readonly originalContent: string,
        public readonly path: string
    ) {
        this.fileType = this.path.split(".").pop() || "md";

        this.content = this.removeFrontmatter(originalContent);
        this.frontmatter = this.parseFrontmatter(originalContent);
    }

    public async save(): Promise<void> {
        if (!this.dirty) {
            return;
        }

        let contentToSave = "";
        if (this.frontmatter) {
            contentToSave += "---\n";
            contentToSave += yaml.dump(this.frontmatter);
            contentToSave += "---\n\n";
        }
        contentToSave += this.content;

        await get(pageRepo).updatePage(this.pageId, contentToSave, this.sha, prompt("Was hast du geändert?", `Updated ${this.pageTitle}`) ?? `Updated ${this.pageTitle}`, this.manager.branch);
        this.dirty = false;
        this.manager.reloadImages();
    }

    public focus(): boolean {
        let index = this.manager.pages.indexOf(this);

        if (index === this.manager.openPageIndex) {
            return true;
        }

        this.manager.openPageIndex = this.manager.pages.indexOf(this);
        return false;
    }

    private parseFrontmatter(content: string): { [key: string]: string | string[] | Date } {
        const lines = content.split("\n");
        let inFrontmatter = false;
        const frontmatterLines: string[] = [];

        for (const line of lines) {
            if (line.trim() === "---") {
                if (inFrontmatter) {
                    break; // End of frontmatter
                }
                inFrontmatter = true;
                continue;
            }
            if (inFrontmatter) {
                frontmatterLines.push(line);
            }
        }

        if (frontmatterLines.length === 0) {
            return {};
        }

        try {
            // You'll need to install js-yaml: npm install js-yaml @types/js-yaml
            return (yaml.load(frontmatterLines.join("\n")) || {}) as { [key: string]: string | string[] | Date };
        } catch (error) {
            console.error("Failed to parse YAML frontmatter:", error);
            return {};
        }
    }

    private removeFrontmatter(content: string): string {
        const lines = content.split("\n");
        let inFrontmatter = false;
        const result: string[] = [];

        for (const line of lines) {
            if (line.trim() === "---") {
                inFrontmatter = !inFrontmatter;
                continue;
            }
            if (!inFrontmatter) {
                result.push(line);
            }
        }

        return result.join("\n").trim();
    }
}

export interface DirTree {
    name: string;
    dirs: { [key: string]: DirTree };
    files: { [key: string]: ListPage };
}

export class PageManager {
    public reloadImages() {
        this.updater = this.updater + 1;
    }
    public branch: string = $state("master");
    public pages: OpenEditPage[] = $state([]);
    public branches: string[] = $state([]);

    constructor() {
        this.reloadBranches();
    }

    public reloadBranches() {
        get(pageRepo)
            .getBranches()
            .then((branches) => {
                this.branches = branches;
            });
    }

    private updater = $state(0);

    public openPageIndex: number = $state(-1);
    public pagesLoad = $derived(get(pageRepo).listPages(this.branch).then(this.convertToTree).then(this._t(this.updater)));
    public imagesLoad = $derived(get(pageRepo).listImages(this.branch).then(this._t(this.updater)));

    private _t<T>(n: number): (v: T) => T {
        return (v: T) => v;
    }

    public selectedPage = $derived(this.openPageIndex >= 0 ? this.pages[this.openPageIndex] : undefined);

    private convertToTree(pages: PageList): DirTree {
        const tree: DirTree = { dirs: {}, files: {}, name: "/" };

        pages.forEach((page) => {
            const pathParts = page.path.split("/").filter((part) => part !== "");
            let current = tree;

            // Navigate/create directory structure
            for (let i = 0; i < pathParts.length - 1; i++) {
                const dir = pathParts[i];
                if (!current.dirs[dir]) {
                    current.dirs[dir] = { dirs: {}, files: {}, name: dir };
                }
                current = current.dirs[dir];
            }

            // Add file to the final directory
            const fileName = pathParts[pathParts.length - 1];
            current.files[fileName] = page;
        });

        return tree;
    }

    public async openPage(pageId: number) {
        const existingPage = this.existingPage(pageId);
        if (existingPage) {
            existingPage.focus();
            return;
        }

        let r = await get(pageRepo).getPage(pageId, this.branch);
        if (!r) {
            return;
        }

        const newPage = new OpenEditPage(this, pageId, r.name, r.sha, new TextDecoder().decode(base64ToBytes(r.content)), r.path);
        this.pages.push(newPage);
        newPage.focus();
    }

    public existingPage(pageId: number): OpenEditPage | undefined {
        return this.pages.find((page) => page.pageId === pageId);
    }

    public closePage(index: number) {
        if (index < 0 || index >= this.pages.length) {
            return;
        }

        const page = this.pages[index];
        if (page.dirty) {
            if (!confirm(`The page "${page.pageTitle}" has unsaved changes. Are you sure you want to close it?`)) {
                return;
            }
        }

        this.pages.splice(index, 1);
        if (this.openPageIndex >= index) {
            this.openPageIndex = Math.max(0, this.openPageIndex - 1);
        }

        if (this.openPageIndex < 0 && this.pages.length > 0) {
            this.openPageIndex = 0;
        }

        if (this.pages.length === 0) {
            this.openPageIndex = -1;
        }
    }

    public async createPage(path: string, newPageName: string): Promise<void> {
        await get(pageRepo).createFile(path, this.branch, newPageName, newPageName);
        this.branch = this.branch;
    }

    public anyUnsavedChanges() {
        return this.pages.some((page) => page.dirty);
    }
}

export const manager = $state(new PageManager());
