import chalk from 'chalk'
import path from 'path'
import fs from 'fs/promises'
import { pathToFileURL } from 'url'

function Node(node) {
    let traits = [
        "labels"
    ]
    for(let i = 0; i < traits.length; i++) {
        if(!node[traits[i]]) {
            throw new Error(`Node is missing field "${traits[i]}": ${JSON.stringify(node)}`)
        }
    }
}

export default class QuillDB {
    #nodes; get nodes() {return this.#nodes};
    #edges; get edges() {return this.#edges};
    #labels = {}; get labels() {return this.#labels}

    constructor() {
        this.loadData()
    }

    async loadData() {
        const dbData = await fs.readFile(path.join(process.cwd(), 'db/db.json'), 'utf8');
        let dbJson;
        try {
            dbJson = JSON.parse(dbData);
        } catch {
            dbJson = []
        }
        this.#nodes = dbJson["nodes"];
        this.#edges = dbJson["edges"];

        let labelModels = await this.getLabelModels();
    
        // Index by label
        for (const [id, entry] of Object.entries(this.#nodes)) {
            Node(entry)
            this.#checkLabelSchemas(id, entry, labelModels)
        }
        for (const [id, entry] of Object.entries(this.#edges)) {
            Edge(entry)
            this.#checkLabelSchemas(id, entry, labelModels)
        }
    
        console.log(chalk.yellow("DB established."))
        Object.preventExtensions(this);
    }

    async getLabelModels() {
        const labelHandlers = {};
        const labelDir = path.join(process.cwd(), 'server/db/model');
        const files = await fs.readdir(labelDir);
    
        for (const file of files) {
            if (!file.endsWith('.js')) continue;
    
            const label = path.basename(file, '.js');
            const modulePath = path.join(labelDir, file);
            const module = await import(pathToFileURL(modulePath).href);
            labelHandlers[label] = module.default;
    
            if (!this.#labels[label]) {
                this.#labels[label] = [];
            }
        }
        return labelHandlers
    }

    #checkLabelSchemas(id, entry, labelModels) {
        entry.labels.forEach(label => {
            const model = labelModels[label];
            if (!model) {
                throw new Error("Data has unknown label or missing model: " + label)
            }
            model(entry);
            this.#labels[label].push(id);
        });
    }

    async getAll() {
        return { nodes: this.#nodes }
    }
}