"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getObjectTypeForPath = getObjectTypeForPath;
exports.loadQueryType = loadQueryType;
exports.getQueryTreeForPath = getQueryTreeForPath;
exports.searchSchema = searchSchema;
exports.getQueriesUptoLevel = getQueriesUptoLevel;
exports.executeQuery = executeQuery;
exports.getSchemaForPath = getSchemaForPath;
exports.enumerateFields = enumerateFields;
const promises_1 = require("node:fs/promises");
const graphql_1 = require("graphql");
const path_1 = __importDefault(require("path"));
const fuse_js_1 = __importDefault(require("fuse.js"));
const client_1 = require("@apollo/client");
;
function getAllSubfields(type, level = 0, maxDepth, includeDescriptions, includeArguments, canGetSubfields) {
    const subfields = {};
    const fields = type.getFields();
    Object.keys(fields).forEach(fieldName => {
        const field = fields[fieldName];
        const fieldType = (0, graphql_1.getNamedType)(field.type);
        subfields[fieldName] = {
            type: fieldType.toString(),
        };
        if (includeDescriptions) {
            subfields[fieldName].description = field.description || "No description available";
        }
        if (includeArguments) {
            subfields[fieldName].arguments = field.args.map(arg => ({
                name: arg.name,
                type: (0, graphql_1.getNamedType)(arg.type).toString(),
                description: arg.description || "No description available"
            }));
        }
        subfields[fieldName].isObjectType = (0, graphql_1.isObjectType)(fieldType);
        if (canGetSubfields && level < maxDepth && (0, graphql_1.isObjectType)(fieldType)) {
            subfields[fieldName].subfields = getAllSubfields(fieldType, level + 1, maxDepth, includeDescriptions, includeArguments, canGetSubfields);
        }
        else if (canGetSubfields && level >= maxDepth && (0, graphql_1.isObjectType)(fieldType)) {
            subfields[fieldName].subfields = "truncated because maxdepth has been reached";
        }
    });
    return subfields;
}
function flattenSchema(type, parentPath = "", level = 0, options) {
    if (!type || !(0, graphql_1.isObjectType)(type))
        return [];
    const fields = type.getFields();
    let result = [];
    Object.keys(fields).forEach(fieldName => {
        const field = fields[fieldName];
        const fieldType = (0, graphql_1.getNamedType)(field.type);
        const fullPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
        let args = [];
        if (options.includeArgs) {
            args = field.args.map(arg => ({
                name: arg.name,
                type: (0, graphql_1.getNamedType)(arg.type).toString(),
                description: options.includeDescriptions ? arg.description || "No description available" : "excluded because includeDescriptions was set to false"
            }));
        }
        else {
            args = ["excluded because includeArgs was set to false"];
        }
        result.push({
            path: fullPath,
            type: fieldType.toString(),
            description: options.includeDescriptions ? field.description || "No description available" : "excluded because includeDescriptions was set to false",
            arguments: args.length > 0 ? args : null
        });
        if ((0, graphql_1.isObjectType)(fieldType) && level !== options.maxDepth) {
            result = result.concat(flattenSchema(fieldType, fullPath, level + 1, options));
        }
    });
    return result;
}
function getObjectTypeForPath(path) {
    return __awaiter(this, void 0, void 0, function* () {
        const queryType = yield loadQueryType();
        if (!path) {
            return queryType;
        }
        if (!queryType)
            return null;
        const pathSegments = path.split(".");
        let currentType = queryType;
        for (const segment of pathSegments) {
            if (!currentType || !(0, graphql_1.isObjectType)(currentType)) {
                return null;
            }
            const field = currentType.getFields()[segment];
            if (!field)
                return null;
            const fieldType = (0, graphql_1.getNamedType)(field.type);
            if (!(0, graphql_1.isObjectType)(fieldType))
                return null;
            currentType = fieldType;
        }
        return currentType;
    });
}
function loadQueryType() {
    return __awaiter(this, void 0, void 0, function* () {
        const schemaFilePath = path_1.default.resolve(__dirname, "shopifyschema.graphqls");
        const schemaSDL = yield (0, promises_1.readFile)(schemaFilePath, "utf8");
        // const ast = parse(schemaSDL);
        const schema = (0, graphql_1.buildSchema)(schemaSDL);
        const queryType = schema.getQueryType();
        return queryType;
    });
}
function extractQueryFields(type, depth = 0, maxLevel) {
    if (!type || !(0, graphql_1.isObjectType)(type) || depth > maxLevel)
        return {};
    const fields = type.getFields();
    let result = {};
    Object.keys(fields).forEach((fieldName) => {
        const field = fields[fieldName];
        const fieldType = (0, graphql_1.getNamedType)(field.type);
        /*
        const args = field.args.map(arg => {
          return {
            name: arg.name,
            type: getNamedType(arg.type).toString(),
            description: arg.description || "No description available"
          };
        });
        */
        result[fieldName] = {
            type: fieldType.toString(),
            description: field.description || "No description available",
            // arguments: args.length > 0 ? args : null,
            subfields: ((0, graphql_1.isObjectType)(fieldType) && depth < maxLevel) ? extractQueryFields(fieldType, depth + 1, maxLevel) : null,
        };
    });
    return result;
}
const client = new client_1.ApolloClient({
    uri: "https://<shopname>.myshopify.com/api/2024-10/graphql.json",
    headers: {
        "X-Shopify-Storefront-Access-Token": "<token>"
    },
    cache: new client_1.InMemoryCache()
});
function getQueryTreeForPath(schema, targetPath, maxDepth, includeArgs, includeDescriptions, strictNoSubfields) {
    if (!schema || !(0, graphql_1.isObjectType)(schema))
        return {};
    const pathParts = targetPath.split(".").filter(it => !!it);
    let currentType = schema;
    let tree = {};
    const treeRoot = tree;
    for (let i = 0; i < pathParts.length; i++) {
        const fieldName = pathParts[i];
        if (!currentType || !(0, graphql_1.isObjectType)(currentType)) {
            console.log(`Field "${fieldName}" not found in schema.`);
            return {};
        }
        const fields = currentType.getFields();
        if (!fields[fieldName]) {
            console.log(`Field "${fieldName}" does not exist.`);
            return {};
        }
        const field = fields[fieldName];
        const fieldType = (0, graphql_1.getNamedType)(field.type);
        tree[fieldName] = {
            name: field.name,
            type: fieldType.toString(),
        };
        if (includeArgs) {
            tree[fieldName].arguments = field.args.map((arg) => ({
                name: arg.name,
                type: (0, graphql_1.getNamedType)(arg.type).toString(),
                description: arg.description || "No description available"
            }));
        }
        if (includeDescriptions) {
            tree[fieldName].description = field.description || "No description available";
        }
        if ((0, graphql_1.isObjectType)(fieldType)) {
            currentType = fieldType;
            tree[fieldName].subfields = {};
            tree = tree[fieldName].subfields;
        }
        else {
            tree = null;
            break;
        }
    }
    if ((0, graphql_1.isObjectType)(currentType) && tree) {
        const subfields = getAllSubfields(currentType, 0, maxDepth, includeDescriptions, includeArgs, !strictNoSubfields);
        if (subfields) {
            Object.assign(tree, subfields);
        }
    }
    return treeRoot;
}
function searchSchema(query, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const queryType = yield getObjectTypeForPath(options.startingPoint);
        if (!queryType) {
            console.log("Schema extraction failure!");
            return;
        }
        const fields = flattenSchema(queryType, options.startingPoint, 0, options);
        const fuse = new fuse_js_1.default(fields, {
            keys: ["path", "description", "type"],
            threshold: 0.3
        });
        const results = fuse.search(query).map(res => res.item);
        // console.log("Search results: ", JSON.stringify(results, null, 2));
        return results;
    });
}
function getQueriesUptoLevel(maxLevel) {
    return __awaiter(this, void 0, void 0, function* () {
        const queryType = yield loadQueryType();
        if (queryType) {
            const allQueries = extractQueryFields(queryType, 0, maxLevel);
            return allQueries;
        }
        else {
            console.log("Schema extraction failure!");
        }
    });
}
function executeQuery(query_1) {
    return __awaiter(this, arguments, void 0, function* (query, variables = {}) {
        try {
            yield client.query({ query: (0, client_1.gql) `${query}`, variables });
            return "successful";
        }
        catch (err) {
            console.error("Graphql Query error: ", err);
            return `error: ${err === null || err === void 0 ? void 0 : err.toString()}`;
        }
    });
}
function getSchemaForPath(path, maxDepth) {
    return __awaiter(this, void 0, void 0, function* () {
        const queryType = yield loadQueryType();
        if (queryType) {
            const tree = yield getQueryTreeForPath(queryType, path, maxDepth, true, true, false);
            return tree;
        }
        else {
            console.log("Failed to extract schema");
        }
    });
}
function enumerateFields(path, includeDescriptions, includeArgs) {
    return __awaiter(this, void 0, void 0, function* () {
        const queryType = yield loadQueryType();
        if (queryType) {
            const result = yield getQueryTreeForPath(queryType, path, 0, includeArgs, includeDescriptions, true);
            return { isSchemaRoot: true, subfields: result };
        }
        else {
            console.log("Failed to extract schema");
        }
    });
}
