"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SourcePlugin = void 0;
const Path = require("path");
const ts = require("typescript");
const index_1 = require("../../models/reflections/index");
const index_2 = require("../../models/sources/index");
const components_1 = require("../components");
const converter_1 = require("../converter");
const utils_1 = require("../../utils");
const nodes_1 = require("../utils/nodes");
const fs_1 = require("../../utils/fs");
const path_1 = require("path");
const assert = require("assert");
/**
 * A handler that attaches source file information to reflections.
 */
let SourcePlugin = class SourcePlugin extends components_1.ConverterComponent {
    constructor() {
        super(...arguments);
        /**
         * A map of all generated {@link SourceFile} instances.
         */
        this.fileMappings = {};
        /**
         * All file names to find the base path from.
         */
        this.fileNames = new Set();
    }
    /**
     * Create a new SourceHandler instance.
     */
    initialize() {
        this.listenTo(this.owner, {
            [converter_1.Converter.EVENT_END]: this.onEnd,
            [converter_1.Converter.EVENT_CREATE_DECLARATION]: this.onDeclaration,
            [converter_1.Converter.EVENT_CREATE_SIGNATURE]: this.onDeclaration,
            [converter_1.Converter.EVENT_RESOLVE_BEGIN]: this.onBeginResolve,
            [converter_1.Converter.EVENT_RESOLVE]: this.onResolve,
            [converter_1.Converter.EVENT_RESOLVE_END]: this.onEndResolve,
        });
    }
    getSourceFile(fileName, project) {
        if (!this.fileMappings[fileName]) {
            const file = new index_2.SourceFile(fileName);
            this.fileMappings[fileName] = file;
            project.files.push(file);
        }
        return this.fileMappings[fileName];
    }
    onEnd() {
        this.fileMappings = {};
        this.fileNames.clear();
        this.basePath = void 0;
    }
    /**
     * Triggered when the converter has created a declaration reflection.
     *
     * Attach the current source file to the {@link DeclarationReflection.sources} array.
     *
     * @param context  The context object describing the current state the converter is in.
     * @param reflection  The reflection that is currently processed.
     * @param node  The node that is currently processed if available.
     */
    onDeclaration(context, reflection, node) {
        if (!node || this.disableSources) {
            return;
        }
        const sourceFile = node.getSourceFile();
        const fileName = sourceFile.fileName;
        this.fileNames.add(fileName);
        const file = this.getSourceFile(fileName, context.project);
        let position;
        if ((0, nodes_1.isNamedNode)(node)) {
            position = ts.getLineAndCharacterOfPosition(sourceFile, node.name.getStart());
        }
        else {
            position = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart());
        }
        if (reflection instanceof index_1.DeclarationReflection) {
            file.reflections.push(reflection);
        }
        if (!reflection.sources) {
            reflection.sources = [];
        }
        reflection.sources.push({
            file: file,
            fileName: fileName,
            line: position.line + 1,
            character: position.character,
        });
    }
    /**
     * Triggered when the converter begins resolving a project.
     *
     * @param context  The context object describing the current state the converter is in.
     */
    onBeginResolve(context) {
        this.basePath = (0, fs_1.getCommonDirectory)([...this.fileNames]);
        for (const file of context.project.files) {
            const fileName = (file.fileName = (0, fs_1.normalizePath)((0, path_1.relative)(this.basePath, file.fileName)));
            this.fileMappings[fileName] = file;
        }
    }
    /**
     * Triggered when the converter resolves a reflection.
     *
     * @param context  The context object describing the current state the converter is in.
     * @param reflection  The reflection that is currently resolved.
     */
    onResolve(_context, reflection) {
        var _a;
        assert(this.basePath != null);
        for (const source of (_a = reflection.sources) !== null && _a !== void 0 ? _a : []) {
            source.fileName = (0, fs_1.normalizePath)((0, path_1.relative)(this.basePath, source.fileName));
        }
    }
    /**
     * Triggered when the converter has finished resolving a project.
     *
     * @param context  The context object describing the current state the converter is in.
     */
    onEndResolve(context) {
        const project = context.project;
        const home = project.directory;
        project.files.forEach((file) => {
            const reflections = [];
            file.reflections.forEach((reflection) => {
                reflections.push(reflection);
            });
            let directory = home;
            const path = Path.dirname(file.fileName);
            if (path !== ".") {
                path.split("/").forEach((pathPiece) => {
                    if (!Object.prototype.hasOwnProperty.call(directory.directories, pathPiece)) {
                        directory.directories[pathPiece] = new index_2.SourceDirectory(pathPiece, directory);
                    }
                    directory = directory.directories[pathPiece];
                });
            }
            directory.files.push(file);
            file.parent = directory;
            file.reflections = reflections;
        });
    }
};
__decorate([
    (0, utils_1.BindOption)("disableSources")
], SourcePlugin.prototype, "disableSources", void 0);
SourcePlugin = __decorate([
    (0, components_1.Component)({ name: "source" })
], SourcePlugin);
exports.SourcePlugin = SourcePlugin;
