"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.default = void 0;
var _constant = require("../constant");
var _utils = require("../utils");
const TYPE_ANOTATION = ": Metadata";
const TYPE_ANOTATION_ASYNC = ": Promise<Metadata> | Metadata";
const TYPE_IMPORT = `import type { Metadata } from 'next'`;
// Find the `export const metadata = ...` node.
function getMetadataExport(fileName, position) {
    const source = (0, _utils).getSource(fileName);
    let metadataExport;
    if (source) {
        const ts = (0, _utils).getTs();
        ts.forEachChild(source, function visit(node) {
            if (metadataExport) return;
            // Covered by this node
            if ((0, _utils).isPositionInsideNode(position, node)) {
                var ref;
                // Export variable
                if (ts.isVariableStatement(node) && ((ref = node.modifiers) == null ? void 0 : ref.some((m)=>m.kind === ts.SyntaxKind.ExportKeyword))) {
                    if (ts.isVariableDeclarationList(node.declarationList)) {
                        for (const declaration of node.declarationList.declarations){
                            if ((0, _utils).isPositionInsideNode(position, declaration) && declaration.name.getText() === "metadata") {
                                // `export const metadata = ...`
                                metadataExport = declaration;
                                return;
                            }
                        }
                    }
                }
            }
        });
    }
    return metadataExport;
}
let cachedProxiedLanguageService;
let cachedProxiedLanguageServiceHost;
function getProxiedLanguageService() {
    if (cachedProxiedLanguageService) return {
        languageService: cachedProxiedLanguageService,
        languageServiceHost: cachedProxiedLanguageServiceHost
    };
    const languageServiceHost = (0, _utils).getInfo().languageServiceHost;
    const ts = (0, _utils).getTs();
    class ProxiedLanguageServiceHost {
        files = {};
        log = ()=>{};
        trace = ()=>{};
        error = ()=>{};
        getCompilationSettings = ()=>languageServiceHost.getCompilationSettings();
        getScriptIsOpen = ()=>true;
        getCurrentDirectory = ()=>languageServiceHost.getCurrentDirectory();
        getDefaultLibFileName = (o)=>languageServiceHost.getDefaultLibFileName(o);
        getScriptVersion = (fileName)=>{
            const file = this.files[fileName];
            if (!file) return languageServiceHost.getScriptVersion(fileName);
            return file.ver.toString();
        };
        getScriptSnapshot = (fileName)=>{
            const file = this.files[fileName];
            if (!file) return languageServiceHost.getScriptSnapshot(fileName);
            return file.file;
        };
        getScriptFileNames() {
            const names = new Set();
            for(var name in this.files){
                if (this.files.hasOwnProperty(name)) {
                    names.add(name);
                }
            }
            const files = languageServiceHost.getScriptFileNames();
            for (const file of files){
                names.add(file);
            }
            return [
                ...names
            ];
        }
        addFile(fileName, body) {
            const snap = ts.ScriptSnapshot.fromString(body);
            snap.getChangeRange = (_)=>undefined;
            const existing = this.files[fileName];
            if (existing) {
                this.files[fileName].ver++;
                this.files[fileName].file = snap;
            } else {
                this.files[fileName] = {
                    ver: 1,
                    file: snap
                };
            }
        }
        readFile(fileName) {
            const file = this.files[fileName];
            return file ? file.file.getText(0, file.file.getLength()) : languageServiceHost.readFile(fileName);
        }
        fileExists(fileName) {
            return this.files[fileName] !== undefined || languageServiceHost.fileExists(fileName);
        }
    }
    cachedProxiedLanguageServiceHost = new ProxiedLanguageServiceHost();
    cachedProxiedLanguageService = ts.createLanguageService(cachedProxiedLanguageServiceHost, ts.createDocumentRegistry());
    return {
        languageService: cachedProxiedLanguageService,
        languageServiceHost: cachedProxiedLanguageServiceHost
    };
}
function updateVirtualFileWithType(fileName, node, isGenerateMetadata) {
    const source = (0, _utils).getSource(fileName);
    if (!source) return;
    // We annotate with the type in a vritual language service
    const sourceText = source.getFullText();
    let nodeEnd;
    let annotation;
    const ts = (0, _utils).getTs();
    if (ts.isFunctionDeclaration(node)) {
        if (isGenerateMetadata) {
            nodeEnd = node.body.getFullStart();
            annotation = TYPE_ANOTATION_ASYNC;
        } else {
            return;
        }
    } else {
        nodeEnd = node.name.getFullStart() + node.name.getFullWidth();
        annotation = TYPE_ANOTATION;
    }
    const newSource = sourceText.slice(0, nodeEnd) + annotation + sourceText.slice(nodeEnd) + TYPE_IMPORT;
    const { languageServiceHost  } = getProxiedLanguageService();
    languageServiceHost.addFile(fileName, newSource);
    return [
        nodeEnd,
        annotation.length
    ];
}
function isTyped(node) {
    return node.type !== undefined;
}
const metadata = {
    filterCompletionsAtPosition (fileName, position, _options, prior) {
        const node = getMetadataExport(fileName, position);
        if (!node) return prior;
        if (isTyped(node)) return prior;
        const ts = (0, _utils).getTs();
        // We annotate with the type in a vritual language service
        const pos = updateVirtualFileWithType(fileName, node);
        if (pos === undefined) return prior;
        // Get completions
        const { languageService  } = getProxiedLanguageService();
        const newPos = position <= pos[0] ? position : position + pos[1];
        const completions = languageService.getCompletionsAtPosition(fileName, newPos, undefined);
        if (completions) {
            completions.isIncomplete = true;
            completions.entries = completions.entries.filter((e)=>{
                return [
                    ts.ScriptElementKind.memberVariableElement,
                    ts.ScriptElementKind.typeElement,
                    ts.ScriptElementKind.string, 
                ].includes(e.kind);
            }).map((e)=>{
                const insertText = e.kind === ts.ScriptElementKind.memberVariableElement && /^[a-zA-Z0-9_]+$/.test(e.name) ? e.name + ": " : e.name;
                return {
                    name: e.name,
                    insertText,
                    kind: e.kind,
                    kindModifiers: e.kindModifiers,
                    sortText: "!" + e.name,
                    labelDetails: {
                        description: `Next.js metadata`
                    },
                    data: e.data
                };
            });
            return completions;
        }
        return prior;
    },
    getSemanticDiagnosticsForExportVariableStatementInClientEntry (fileName, node) {
        const source = (0, _utils).getSource(fileName);
        const ts = (0, _utils).getTs();
        // It is not allowed to export `metadata` or `generateMetadata` in client entry
        if (ts.isFunctionDeclaration(node)) {
            var ref;
            if (((ref = node.name) == null ? void 0 : ref.getText()) === "generateMetadata") {
                return [
                    {
                        file: source,
                        category: ts.DiagnosticCategory.Error,
                        code: _constant.NEXT_TS_ERRORS.INVALID_METADATA_EXPORT,
                        messageText: `The Next.js 'generateMetadata' API is not allowed in a client component.`,
                        start: node.name.getStart(),
                        length: node.name.getWidth()
                    }, 
                ];
            }
        } else {
            for (const declaration of node.declarationList.declarations){
                const name = declaration.name.getText();
                if (name === "metadata") {
                    return [
                        {
                            file: source,
                            category: ts.DiagnosticCategory.Error,
                            code: _constant.NEXT_TS_ERRORS.INVALID_METADATA_EXPORT,
                            messageText: `The Next.js 'metadata' API is not allowed in a client component.`,
                            start: declaration.name.getStart(),
                            length: declaration.name.getWidth()
                        }, 
                    ];
                }
            }
        }
        return [];
    },
    getSemanticDiagnosticsForExportVariableStatement (fileName, node) {
        const source = (0, _utils).getSource(fileName);
        const ts = (0, _utils).getTs();
        function proxyDiagnostics(pos, n) {
            // Get diagnostics
            const { languageService  } = getProxiedLanguageService();
            const diagnostics = languageService.getSemanticDiagnostics(fileName);
            // Filter and map the results
            return diagnostics.filter((d)=>{
                if (d.start === undefined || d.length === undefined) return false;
                if (d.start < n.getFullStart()) return false;
                if (d.start + d.length >= n.getFullStart() + n.getFullWidth() + pos[1]) return false;
                return true;
            }).map((d)=>{
                return {
                    file: source,
                    category: d.category,
                    code: d.code,
                    messageText: d.messageText,
                    start: d.start < pos[0] ? d.start : d.start - pos[1],
                    length: d.length
                };
            });
        }
        if (ts.isFunctionDeclaration(node)) {
            var ref;
            if (((ref = node.name) == null ? void 0 : ref.getText()) === "generateMetadata") {
                if (isTyped(node)) return [];
                // We annotate with the type in a vritual language service
                const pos = updateVirtualFileWithType(fileName, node, true);
                if (!pos) return [];
                return proxyDiagnostics(pos, node);
            }
        } else {
            for (const declaration of node.declarationList.declarations){
                if (declaration.name.getText() === "metadata") {
                    if (isTyped(declaration)) break;
                    // We annotate with the type in a vritual language service
                    const pos = updateVirtualFileWithType(fileName, declaration);
                    if (!pos) break;
                    return proxyDiagnostics(pos, declaration);
                }
            }
        }
        return [];
    },
    getCompletionEntryDetails (fileName, position, entryName, formatOptions, source, preferences, data) {
        const node = getMetadataExport(fileName, position);
        if (!node) return;
        if (isTyped(node)) return;
        // We annotate with the type in a vritual language service
        const pos = updateVirtualFileWithType(fileName, node);
        if (pos === undefined) return;
        const { languageService  } = getProxiedLanguageService();
        const newPos = position <= pos[0] ? position : position + pos[1];
        const details = languageService.getCompletionEntryDetails(fileName, newPos, entryName, formatOptions, source, preferences, data);
        return details;
    },
    getQuickInfoAtPosition (fileName, position) {
        const node = getMetadataExport(fileName, position);
        if (!node) return;
        if (isTyped(node)) return;
        // We annotate with the type in a vritual language service
        const pos = updateVirtualFileWithType(fileName, node);
        if (pos === undefined) return;
        const { languageService  } = getProxiedLanguageService();
        const newPos = position <= pos[0] ? position : position + pos[1];
        const insight = languageService.getQuickInfoAtPosition(fileName, newPos);
        return insight;
    }
};
var _default = metadata;
exports.default = _default;

//# sourceMappingURL=metadata.js.map