"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.create = create;
exports.getLastImportNode = getLastImportNode;
exports.createAddComponentToOptionEdit = createAddComponentToOptionEdit;
const language_core_1 = require("@vue/language-core");
const vscode_uri_1 = require("vscode-uri");
const utils_1 = require("../utils");
const unicodeRE = /\\u/g;
function create(ts, { collectExtractProps }) {
    return {
        name: 'vue-extract-file',
        capabilities: {
            codeActionProvider: {
                codeActionKinds: ['refactor'],
                resolveProvider: true,
            },
        },
        create(context) {
            return {
                provideCodeActions(document, range, ctx) {
                    if (ctx.only && !ctx.only.includes('refactor')) {
                        return;
                    }
                    const startOffset = document.offsetAt(range.start);
                    const endOffset = document.offsetAt(range.end);
                    if (startOffset === endOffset) {
                        return;
                    }
                    const info = (0, utils_1.resolveEmbeddedCode)(context, document.uri);
                    if (info?.code.id !== 'template') {
                        return;
                    }
                    const { ir } = info.root;
                    const script = ir.scriptSetup ?? ir.script;
                    if (!ir.template || !script) {
                        return;
                    }
                    const templateCodeRange = selectTemplateCode(startOffset, endOffset, ir.template);
                    if (!templateCodeRange) {
                        return;
                    }
                    return [
                        {
                            title: 'Extract into new dumb component',
                            kind: 'refactor.move.newFile.dumb',
                            data: {
                                uri: document.uri,
                                range: [startOffset, endOffset],
                                newName: 'NewComponent',
                            },
                        },
                    ];
                },
                async resolveCodeAction(codeAction) {
                    const { uri, range, newName } = codeAction.data;
                    const [startOffset, endOffset] = range;
                    const info = (0, utils_1.resolveEmbeddedCode)(context, uri);
                    if (info?.code.id !== 'template') {
                        return codeAction;
                    }
                    const { ir } = info.root;
                    const script = ir.scriptSetup ?? ir.script;
                    if (!ir.template || !script) {
                        return codeAction;
                    }
                    const templateCodeRange = selectTemplateCode(startOffset, endOffset, ir.template);
                    if (!templateCodeRange) {
                        return codeAction;
                    }
                    const toExtract = await collectExtractProps(info.root.fileName, templateCodeRange) ?? [];
                    const templateInitialIndent = await context.env.getConfiguration('vue.format.template.initialIndent') ?? true;
                    const scriptInitialIndent = await context.env.getConfiguration('vue.format.script.initialIndent')
                        ?? false;
                    const document = context.documents.get(vscode_uri_1.URI.parse(uri), info.code.languageId, info.code.snapshot);
                    const sfcDocument = context.documents.get(info.script.id, info.script.languageId, info.script.snapshot);
                    const newUri = sfcDocument.uri.slice(0, sfcDocument.uri.lastIndexOf('/') + 1) + `${newName}.vue`;
                    const lastImportNode = getLastImportNode(ts, script.ast);
                    let newFileTags = [];
                    newFileTags.push(constructTag('template', [], templateInitialIndent, ir.template.content.slice(templateCodeRange[0], templateCodeRange[1])));
                    if (toExtract.length) {
                        newFileTags.push(constructTag('script', ['setup', 'lang="ts"'], scriptInitialIndent, generateNewScriptContents()));
                    }
                    if (ir.template.startTagEnd > script.startTagEnd) {
                        newFileTags = newFileTags.reverse();
                    }
                    const templateEdits = [
                        {
                            range: {
                                start: document.positionAt(templateCodeRange[0]),
                                end: document.positionAt(templateCodeRange[1]),
                            },
                            newText: generateReplaceTemplate(),
                        },
                    ];
                    const sfcEdits = [
                        {
                            range: lastImportNode
                                ? {
                                    start: sfcDocument.positionAt(script.startTagEnd + lastImportNode.end),
                                    end: sfcDocument.positionAt(script.startTagEnd + lastImportNode.end),
                                }
                                : {
                                    start: sfcDocument.positionAt(script.startTagEnd),
                                    end: sfcDocument.positionAt(script.startTagEnd),
                                },
                            newText: `\nimport ${newName} from './${newName}.vue'`,
                        },
                    ];
                    if (ir.script) {
                        const edit = createAddComponentToOptionEdit(ts, ir, ir.script.ast, newName);
                        if (edit) {
                            sfcEdits.push({
                                range: {
                                    start: sfcDocument.positionAt(ir.script.startTagEnd + edit.range.start),
                                    end: sfcDocument.positionAt(ir.script.startTagEnd + edit.range.end),
                                },
                                newText: edit.newText,
                            });
                        }
                    }
                    return {
                        ...codeAction,
                        edit: {
                            documentChanges: [
                                // editing template virtual document
                                {
                                    textDocument: {
                                        uri: document.uri,
                                        version: null,
                                    },
                                    edits: templateEdits,
                                },
                                // editing vue sfc
                                {
                                    textDocument: {
                                        uri: info.script.id.toString(),
                                        version: null,
                                    },
                                    edits: sfcEdits,
                                },
                                // creating new file with content
                                {
                                    uri: newUri,
                                    kind: 'create',
                                },
                                {
                                    textDocument: {
                                        uri: newUri,
                                        version: null,
                                    },
                                    edits: [
                                        {
                                            range: {
                                                start: { line: 0, character: 0 },
                                                end: { line: 0, character: 0 },
                                            },
                                            newText: newFileTags.join('\n'),
                                        },
                                    ],
                                },
                            ],
                        },
                    };
                    function generateNewScriptContents() {
                        const lines = [];
                        const props = toExtract.filter(p => !p.model);
                        const models = toExtract.filter(p => p.model);
                        if (props.length) {
                            lines.push(`defineProps<{\n\t${props.map(p => `${p.name}: ${p.type};`).join('\n\t')}\n}>()`);
                        }
                        for (const model of models) {
                            lines.push(`const ${model.name} = defineModel<${model.type}>('${model.name}', { required: true })`);
                        }
                        return lines.join('\n');
                    }
                    function generateReplaceTemplate() {
                        const props = toExtract.filter(p => !p.model);
                        const models = toExtract.filter(p => p.model);
                        return [
                            `<${newName}`,
                            ...props.map(p => `:${p.name}="${p.name}"`),
                            ...models.map(p => `v-model:${p.name}="${p.name}"`),
                            `/>`,
                        ].join(' ');
                    }
                },
            };
        },
    };
}
function selectTemplateCode(startOffset, endOffset, templateBlock) {
    const insideNodes = [];
    templateBlock.ast?.children.forEach(function visit(node) {
        if (node.loc.start.offset >= startOffset
            && node.loc.end.offset <= endOffset) {
            insideNodes.push(node);
        }
        if ('children' in node) {
            node.children.forEach(node => {
                if (typeof node === 'object') {
                    visit(node);
                }
            });
        }
        else if ('branches' in node) {
            node.branches.forEach(visit);
        }
        else if ('content' in node) {
            if (typeof node.content === 'object') {
                visit(node.content);
            }
        }
    });
    if (insideNodes.length) {
        const first = insideNodes.sort((a, b) => a.loc.start.offset - b.loc.start.offset)[0];
        const last = insideNodes.sort((a, b) => b.loc.end.offset - a.loc.end.offset)[0];
        return [first.loc.start.offset, last.loc.end.offset];
    }
}
function constructTag(name, attributes, initialIndent, content) {
    if (initialIndent) {
        content = content.split('\n').map(line => `\t${line}`).join('\n');
    }
    const attributesString = attributes.length ? ` ${attributes.join(' ')}` : '';
    return `<${name}${attributesString}>\n${content}\n</${name}>\n`;
}
function getLastImportNode(ts, sourceFile) {
    let lastImportNode;
    for (const statement of sourceFile.statements) {
        if (ts.isImportDeclaration(statement)) {
            lastImportNode = statement;
        }
        else {
            break;
        }
    }
    return lastImportNode;
}
function createAddComponentToOptionEdit(ts, ir, ast, componentName) {
    const componentOptions = language_core_1.tsCodegen.get(ir)?.getScriptRanges()?.exportDefault?.options;
    if (!componentOptions) {
        return;
    }
    // https://github.com/microsoft/TypeScript/issues/36174
    const printer = ts.createPrinter();
    if (componentOptions.components) {
        const newNode = {
            ...componentOptions.components.node,
            properties: [
                ...componentOptions.components.node.properties,
                ts.factory.createShorthandPropertyAssignment(componentName),
            ],
        };
        const printText = printer.printNode(ts.EmitHint.Expression, newNode, ast);
        return {
            range: componentOptions.components,
            newText: unescape(printText.replace(unicodeRE, '%u')),
        };
    }
    else {
        const newNode = {
            ...componentOptions.args.node,
            properties: [
                ...componentOptions.args.node.properties,
                ts.factory.createShorthandPropertyAssignment(`components: { ${componentName} }`),
            ],
        };
        const printText = printer.printNode(ts.EmitHint.Expression, newNode, ast);
        return {
            range: componentOptions.args,
            newText: unescape(printText.replace(unicodeRE, '%u')),
        };
    }
}
//# sourceMappingURL=vue-extract-file.js.map