"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createVueTypeScriptReader = void 0;
const vfs_1 = require("@previewjs/vfs");
const path_1 = __importDefault(require("path"));
const typescript_1 = __importDefault(require("typescript"));
const vue_template_compiler_1 = require("vue-template-compiler");
/**
 * Returns a reader that returns a virtual TypeScript file
 * named foo.vue.ts for every corresponding foo.vue file.
 */
function createVueTypeScriptReader(reader) {
    return new VueTypeScriptReader(reader);
}
exports.createVueTypeScriptReader = createVueTypeScriptReader;
class VueTypeScriptReader {
    constructor(reader) {
        var _a;
        this.reader = reader;
        this.listeners = new vfs_1.ReaderListeners();
        reader.listeners.add({
            onChange: (absoluteFilePath, info) => {
                this.listeners.notify(absoluteFilePath, info);
            },
        });
        this.observe = (_a = reader.observe) === null || _a === void 0 ? void 0 : _a.bind(reader);
    }
    async read(absoluteFilePath) {
        if (absoluteFilePath.endsWith(".vue.ts")) {
            const source = await this.reader.read(absoluteFilePath.substr(0, absoluteFilePath.length - 3));
            if ((source === null || source === void 0 ? void 0 : source.kind) !== "file") {
                return null;
            }
            const name = path_1.default.basename(absoluteFilePath);
            return {
                kind: "file",
                name,
                realPath: async () => null,
                lastModifiedMillis: () => source.lastModifiedMillis(),
                read: async () => convertToTypeScript(await source.read(), name),
                size: () => source.size(),
            };
        }
        return this.reader.read(absoluteFilePath);
    }
    readSync(absoluteFilePath) {
        if (absoluteFilePath.endsWith(".vue.ts")) {
            const source = this.reader.readSync(absoluteFilePath.substr(0, absoluteFilePath.length - 3));
            if ((source === null || source === void 0 ? void 0 : source.kind) !== "file") {
                return null;
            }
            const name = path_1.default.basename(absoluteFilePath);
            return {
                kind: "file",
                name,
                realPath: () => null,
                lastModifiedMillis: () => source.lastModifiedMillis(),
                read: () => convertToTypeScript(source.read(), name),
                size: () => source.size(),
            };
        }
        const entry = this.reader.readSync(absoluteFilePath);
        if ((entry === null || entry === void 0 ? void 0 : entry.kind) === "file" &&
            (entry.name.endsWith(".jsx") || entry.name.endsWith(".tsx"))) {
            return {
                ...entry,
                read: () => `/// <reference types="vue/types/jsx" />\n${entry.read()}`,
            };
        }
        return entry;
    }
}
function convertToTypeScript(vueTemplateSource, name) {
    var _a, _b;
    const parsed = (0, vue_template_compiler_1.parseComponent)(vueTemplateSource);
    const scriptContent = (_a = parsed.script) === null || _a === void 0 ? void 0 : _a.content;
    if (!scriptContent) {
        return "";
    }
    const lang = (_b = parsed.script) === null || _b === void 0 ? void 0 : _b.lang;
    if (lang && !["js", "javascript", "ts", "typescript"].includes(lang)) {
        return "";
    }
    const sourceFile = typescript_1.default.createSourceFile(`${name}.vue.ts`, scriptContent, typescript_1.default.ScriptTarget.Latest);
    const transformation = typescript_1.default.transform(sourceFile, [
        (context) => (sourceFile) => {
            if (typescript_1.default.isSourceFile(sourceFile)) {
                return typescript_1.default.visitEachChild(sourceFile, (node) => {
                    var _a, _b;
                    if (typescript_1.default.isExportAssignment(node)) {
                        return typescript_1.default.factory.createVariableStatement([], typescript_1.default.factory.createVariableDeclarationList([
                            typescript_1.default.factory.createVariableDeclaration("pjs_component", undefined, undefined, typescript_1.default.factory.createAsExpression(node.expression, typescript_1.default.factory.createTypeReferenceNode("const"))),
                        ], typescript_1.default.NodeFlags.Const));
                    }
                    if (typescript_1.default.isClassDeclaration(node) &&
                        ((_a = node.modifiers) === null || _a === void 0 ? void 0 : _a.find((m) => m.kind === typescript_1.default.SyntaxKind.ExportKeyword)) &&
                        ((_b = node.modifiers) === null || _b === void 0 ? void 0 : _b.find((m) => m.kind === typescript_1.default.SyntaxKind.DefaultKeyword))) {
                        // When we encounter a class declaration that uses vue-property-decorator,
                        // we'll transform it into a pjs_component definition.
                        //
                        // For example:
                        //
                        // @Component
                        // export default class Button extends Vue {
                        //   @Prop({ type: String, default: 'default' }) readonly size!: ButtonSize;
                        // }
                        //
                        // will become:
                        //
                        // const pjs_component = {
                        //   props: {
                        //     size: {
                        //       default: "default",
                        //       type: (): Size => { return undefined as any; }
                        //     }
                        //   }
                        // } as const;
                        const extractedProps = [];
                        for (const member of node.members) {
                            if (typescript_1.default.isPropertyDeclaration(member)) {
                                const [decorator] = typescript_1.default.canHaveDecorators(member)
                                    ? typescript_1.default.getDecorators(member) || []
                                    : [];
                                if (decorator &&
                                    typescript_1.default.isCallExpression(decorator.expression) &&
                                    typescript_1.default.isIdentifier(decorator.expression.expression) &&
                                    decorator.expression.expression.text === "Prop") {
                                    const [options] = decorator.expression.arguments;
                                    if (options && typescript_1.default.isObjectLiteralExpression(options)) {
                                        if (member.type) {
                                            extractedProps.push(typescript_1.default.factory.createPropertyAssignment(member.name, typescript_1.default.factory.createObjectLiteralExpression([
                                                ...options.properties.filter((p) => p.name &&
                                                    typescript_1.default.isIdentifier(p.name) &&
                                                    p.name.text !== "type"),
                                                typescript_1.default.factory.createPropertyAssignment("type", typescript_1.default.factory.createArrowFunction([], [], [], member.type, undefined, typescript_1.default.factory.createBlock([
                                                    typescript_1.default.factory.createReturnStatement(typescript_1.default.factory.createAsExpression(typescript_1.default.factory.createIdentifier("undefined"), typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.AnyKeyword))),
                                                ]))),
                                            ])));
                                        }
                                        else {
                                            extractedProps.push(typescript_1.default.factory.createPropertyAssignment(member.name, options));
                                        }
                                    }
                                }
                            }
                        }
                        return typescript_1.default.factory.createVariableStatement([], typescript_1.default.factory.createVariableDeclarationList([
                            typescript_1.default.factory.createVariableDeclaration("pjs_component", undefined, undefined, typescript_1.default.factory.createAsExpression(typescript_1.default.factory.createObjectLiteralExpression([
                                typescript_1.default.factory.createPropertyAssignment("props", typescript_1.default.factory.createObjectLiteralExpression(extractedProps)),
                            ]), typescript_1.default.factory.createTypeReferenceNode("const"))),
                        ], typescript_1.default.NodeFlags.Const));
                    }
                    return node;
                }, context);
            }
            return sourceFile;
        },
    ]);
    const transformed = typescript_1.default
        .createPrinter()
        .printFile(transformation.transformed[0]);
    transformation.dispose();
    return `
${transformed}

import type {Prop as PJS_Prop} from 'vue/types/options';

type PJS_TypeOrUnion<T> = PJS_Prop<T> | ReadonlyArray<PJS_Prop<T>>;
type PJS_OptionalPropType<T> = PJS_TypeOrUnion<T> | {type: PJS_TypeOrUnion<T>; required?: false};
type PJS_RequiredPropType<T> = {type: PJS_TypeOrUnion<T>; required: true};
type PJS_OptionalPropsKeys<T> = {
  [K in keyof T]: T[K] extends PJS_OptionalPropType<any> ? K : never;
}[keyof T];
type PJS_RequiredPropsKeys<T> = {
  [K in keyof T]: T[K] extends PJS_RequiredPropType<any> ? K : never;
}[keyof T];
type PJS_CombinedProps<T> = T extends readonly [...any] ? {
  [K in T[number]]: unknown
} : ({
  [K in PJS_OptionalPropsKeys<T>]?: T[K] extends PJS_OptionalPropType<infer S> ? S : never;
} & {
  [K in PJS_RequiredPropsKeys<T>]: T[K] extends PJS_RequiredPropType<infer S> ? S : never;
});
type PJS_ExtractProps<T> = T extends { props: any } ? PJS_CombinedProps<T['props']> : {}
type PJS_Props = PJS_ExtractProps<typeof pjs_component>;
`;
}
