import fs from 'fs';
import path from 'path';
import { parse as yamlParse } from 'yaml';
import { CHAIN_FILE_REGEX, SCHEMA_REF, WARP_ROUTE_CONFIG_FILE_REGEX, WARP_ROUTE_DEPLOY_FILE_REGEX, } from '../consts.js';
import { ChainAddressesSchema, } from '../types.js';
import { toYamlString } from '../utils.js';
import { RegistryType, } from '../registry/IRegistry.js';
import { SynchronousRegistry } from '../registry/SynchronousRegistry.js';
import { warpRouteConfigPathToId, warpRouteDeployConfigPathToId } from '../registry/warp-utils.js';
/**
 * A registry that uses a local file system path as its data source.
 * Requires file system access so it cannot be used in the browser.
 */
export class FileSystemRegistry extends SynchronousRegistry {
    type = RegistryType.FileSystem;
    constructor(options) {
        super(options);
    }
    getUri(itemPath) {
        if (!itemPath)
            return super.getUri();
        return path.join(this.uri, itemPath);
    }
    /**
     * Retrieves filepaths for chains, warp core, and warp deploy configs
     */
    listRegistryContent() {
        if (this.listContentCache)
            return this.listContentCache;
        const chainFileList = this.listFiles(path.join(this.uri, this.getChainsPath()));
        const chains = {};
        for (const filePath of chainFileList) {
            const matches = filePath.match(CHAIN_FILE_REGEX);
            if (!matches)
                continue;
            const [_, chainName, fileName] = matches;
            chains[chainName] ??= {};
            // @ts-ignore allow dynamic key assignment
            chains[chainName][fileName] = filePath;
        }
        const warpRoutes = {};
        const warpRouteFiles = this.listFiles(path.join(this.uri, this.getWarpRoutesPath()));
        for (const filePath of warpRouteFiles) {
            if (!WARP_ROUTE_CONFIG_FILE_REGEX.test(filePath))
                continue;
            const routeId = warpRouteConfigPathToId(filePath);
            warpRoutes[routeId] = filePath;
        }
        const warpDeployConfig = {};
        const warpDeployFiles = this.listFiles(path.join(this.uri, this.getWarpRoutesPath()));
        for (const filePath of warpDeployFiles) {
            if (!WARP_ROUTE_DEPLOY_FILE_REGEX.test(filePath))
                continue;
            const routeId = warpRouteDeployConfigPathToId(filePath);
            warpDeployConfig[routeId] = filePath;
        }
        return (this.listContentCache = { chains, deployments: { warpRoutes, warpDeployConfig } });
    }
    getMetadata() {
        if (this.metadataCache)
            return this.metadataCache;
        const chainMetadata = {};
        const repoContents = this.listRegistryContent();
        for (const [chainName, chainFiles] of Object.entries(repoContents.chains)) {
            if (!chainFiles.metadata)
                continue;
            const data = fs.readFileSync(chainFiles.metadata, 'utf8');
            chainMetadata[chainName] = yamlParse(data);
        }
        return (this.metadataCache = chainMetadata);
    }
    getAddresses() {
        if (this.addressCache)
            return this.addressCache;
        const chainAddresses = {};
        const repoContents = this.listRegistryContent();
        for (const [chainName, chainFiles] of Object.entries(repoContents.chains)) {
            if (!chainFiles.addresses)
                continue;
            const data = fs.readFileSync(chainFiles.addresses, 'utf8');
            chainAddresses[chainName] = ChainAddressesSchema.parse(yamlParse(data));
        }
        return (this.addressCache = chainAddresses);
    }
    removeChain(chainName) {
        const chainFiles = this.listRegistryContent().chains[chainName];
        super.removeChain(chainName);
        this.removeFiles(Object.values(chainFiles));
    }
    addWarpRoute(config, options) {
        const filePath = path.join(this.uri, this.getWarpRouteCoreConfigPath(config, options));
        this.createFile({
            filePath,
            data: toYamlString(config, SCHEMA_REF),
        });
    }
    addWarpRouteConfig(warpConfig, options) {
        const filePath = path.join(this.uri, this.getWarpRouteDeployConfigPath(warpConfig, options));
        this.createFile({
            filePath,
            data: toYamlString(warpConfig),
        });
    }
    listFiles(dirPath) {
        if (!fs.existsSync(dirPath))
            return [];
        const entries = fs.readdirSync(dirPath, { withFileTypes: true });
        const filePaths = entries.map((entry) => {
            const fullPath = path.join(dirPath, entry.name);
            return entry.isDirectory() ? this.listFiles(fullPath) : fullPath;
        });
        return filePaths.flat();
    }
    createOrUpdateChain(chain) {
        if (!chain.metadata && !chain.addresses)
            throw new Error(`Chain ${chain.chainName} must have metadata or addresses, preferably both`);
        const currentChains = this.listRegistryContent();
        if (!currentChains.chains[chain.chainName]) {
            this.logger.debug(`Chain ${chain.chainName} not found in registry, adding it now`);
        }
        if (chain.metadata) {
            this.createChainFile(chain.chainName, 'metadata', chain.metadata, this.getMetadata(), SCHEMA_REF);
        }
        if (chain.addresses) {
            this.createChainFile(chain.chainName, 'addresses', chain.addresses, this.getAddresses());
        }
    }
    createChainFile(chainName, fileName, data, cache, prefix) {
        const filePath = path.join(this.uri, this.getChainsPath(), chainName, `${fileName}.yaml`);
        const currentChains = this.listRegistryContent().chains;
        currentChains[chainName] ||= {};
        currentChains[chainName][fileName] = filePath;
        cache[chainName] = data;
        this.createFile({ filePath, data: toYamlString(data, prefix) });
    }
    createFile(file) {
        const dirPath = path.dirname(file.filePath);
        if (!fs.existsSync(dirPath))
            fs.mkdirSync(dirPath, {
                recursive: true,
            });
        fs.writeFileSync(file.filePath, file.data);
    }
    removeFiles(filePaths) {
        for (const filePath of filePaths) {
            fs.unlinkSync(filePath);
        }
        const parentDir = path.dirname(filePaths[0]);
        if (fs.readdirSync(parentDir).length === 0) {
            fs.rmdirSync(parentDir);
        }
    }
    getWarpRoutesForIds(ids) {
        const warpRoutes = this.listRegistryContent().deployments.warpRoutes;
        return this.readConfigsForIds(ids, warpRoutes);
    }
    getWarpDeployConfigForIds(ids) {
        const warpDeployConfig = this.listRegistryContent().deployments.warpDeployConfig;
        return this.readConfigsForIds(ids, warpDeployConfig);
    }
    /**
     * Reads config files for the given WarpRouteIds.
     * @param ids - The WarpRouteIds to read configs for.
     * @param configURIs - A mapping of WarpRouteIds to file paths where the configs are stored.
     * @returns An array of config objects.
     */
    readConfigsForIds(ids, configURIs) {
        const configs = [];
        for (const [id, filePath] of Object.entries(configURIs)) {
            if (!ids.includes(id))
                continue;
            const data = fs.readFileSync(filePath, 'utf8');
            configs.push(yamlParse(data));
        }
        return configs;
    }
}
