'use strict';

var chalk = require('chalk');
var inquirer = require('inquirer');
var csv = require('csvtojson');
var fetch = require('node-fetch');
var fs = require('fs');
var sdk = require('@linear/sdk');
var dateFns = require('date-fns');
var _ = require('lodash');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

function _interopNamespace(e) {
    if (e && e.__esModule) return e;
    var n = Object.create(null);
    if (e) {
        Object.keys(e).forEach(function (k) {
            if (k !== 'default') {
                var d = Object.getOwnPropertyDescriptor(e, k);
                Object.defineProperty(n, k, d.get ? d : {
                    enumerable: true,
                    get: function () { return e[k]; }
                });
            }
        });
    }
    n["default"] = e;
    return Object.freeze(n);
}

var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
var inquirer__namespace = /*#__PURE__*/_interopNamespace(inquirer);
var csv__default = /*#__PURE__*/_interopDefaultLegacy(csv);
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var ___default = /*#__PURE__*/_interopDefaultLegacy(_);

/******************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */

function __rest(s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
}

function __awaiter(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());
    });
}

typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
    var e = new Error(message);
    return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};

// eslint-disable-next-line @typescript-eslint/no-var-requires
const j2m$2 = require("jira2md");
/**
 * Import issues from an Asana CSV export.
 *
 * @param filePath  path to csv file
 * @param orgSlug   base Asana project url
 */
class AsanaCsvImporter {
    constructor(filePath, orgSlug) {
        this.import = () => __awaiter(this, void 0, void 0, function* () {
            const data = (yield csv__default["default"]().fromFile(this.filePath));
            const importData = {
                issues: [],
                labels: {},
                users: {},
                statuses: {},
            };
            const assignees = Array.from(new Set(data.map(row => row.Assignee)));
            for (const user of assignees) {
                importData.users[user] = {
                    name: user,
                };
            }
            for (const row of data) {
                const title = row.Name;
                if (!title) {
                    continue;
                }
                const url = this.organizationName ? `${this.organizationName}${row["Task ID"]}` : undefined;
                const mdDesc = j2m$2.to_markdown(row.Notes);
                const description = url ? `${mdDesc}\n\n[View original issue in Asana](${url})` : mdDesc;
                const priority = mapPriority$2(row.Priority);
                const dueDate = row["Due Date"] ? new Date(row["Due Date"]) : undefined;
                const tags = row.Tags.split(",");
                const assigneeId = row.Assignee && row.Assignee.length > 0 ? row.Assignee : undefined;
                const status = !!row["Completed At"] ? "Done" : "Todo";
                const labels = tags.filter(tag => !!tag);
                importData.issues.push({
                    title,
                    description,
                    status,
                    priority,
                    url,
                    assigneeId,
                    labels,
                    dueDate,
                });
                for (const lab of labels) {
                    if (!importData.labels[lab]) {
                        importData.labels[lab] = {
                            name: lab,
                        };
                    }
                }
            }
            return importData;
        });
        this.filePath = filePath;
        this.organizationName = orgSlug;
    }
    get name() {
        return "Asana (CSV)";
    }
    get defaultTeamName() {
        return "Asana";
    }
}
const mapPriority$2 = (input) => {
    const priorityMap = {
        High: 2,
        Med: 3,
        Low: 4,
    };
    return priorityMap[input] || 0;
};

const BASE_PATH$5 = process.cwd();
const ASANA_URL_REGEX = /(^https?:\/\/app.asana.com\/0\/\d+\/)list/;
const asanaCsvImport = () => __awaiter(void 0, void 0, void 0, function* () {
    var _a;
    const answers = yield inquirer__namespace.prompt(questions$6);
    const orgSlug = (_a = answers.asanaUrlName.match(ASANA_URL_REGEX)) === null || _a === void 0 ? void 0 : _a[1];
    const asanaImporter = new AsanaCsvImporter(answers.asanaFilePath, orgSlug !== null && orgSlug !== void 0 ? orgSlug : "");
    return asanaImporter;
});
const questions$6 = [
    {
        basePath: BASE_PATH$5,
        type: "filePath",
        name: "asanaFilePath",
        message: "Select your exported CSV file of Asana issues",
    },
    {
        type: "input",
        name: "asanaUrlName",
        message: "Input the URL of your Asana board (e.g. https://app.asana.com/0/123456789/list):",
        validate: (input) => {
            return !!input.match(ASANA_URL_REGEX);
        },
    },
];

const GITHUB_API = "https://api.github.com/graphql";
const githubClient = (apiKey) => {
    return (query, variables) => __awaiter(void 0, void 0, void 0, function* () {
        const res = yield fetch__default["default"](GITHUB_API, {
            method: "POST",
            headers: {
                authorization: `token ${apiKey}`,
            },
            body: JSON.stringify({
                query,
                variables,
            }),
        });
        const data = yield res.json();
        return data.data;
    });
};

/**
 * Fetch and paginate through all Github issues.
 *
 * @param apiKey GitHub api key for authentication
 */
class GithubImporter {
    constructor(apiKey, owner, repo) {
        this.import = () => __awaiter(this, void 0, void 0, function* () {
            let issueData = [];
            let cursor;
            const github = githubClient(this.apiKey);
            while (true) {
                try {
                    const data = (yield github(`query lastIssues($owner: String!, $repo: String!, $num: Int, $cursor: String) {
            repository(owner:$owner, name:$repo) {
              issues(first:$num, after: $cursor, states:OPEN) {
                edges {
                  node {
                    id
                    title
                    body
                    url
                    createdAt
                    labels(first:100) {
                      nodes{
                        id
                        color
                        name
                        description
                      }
                    }
                    comments(first: 100) {
                      nodes {
                        id
                        body
                        createdAt
                        url
                        author {
                          login
                          avatarUrl(size: 255)
                          ... on User {
                            id
                            name
                            email
                          }                        
                        }
                      }
                    }
                  }
                }
                pageInfo {
                  hasNextPage
                  endCursor
                }
              }
            }
          }`, {
                        owner: this.owner,
                        repo: this.repo,
                        num: 25,
                        cursor,
                    }));
                    // User didn't select repo scope
                    if (!data || !data.repository) {
                        throw new Error(`Unable to find repo ${this.owner}/${this.repo}. Did you select \`repo\` scope for your GitHub token?`);
                    }
                    cursor = data.repository.issues.pageInfo.endCursor;
                    const fetchedIssues = data.repository.issues.edges.map((d) => d.node);
                    issueData = issueData.concat(fetchedIssues);
                    if (!data.repository.issues.pageInfo.hasNextPage) {
                        break;
                    }
                }
                catch (err) {
                    console.error(err);
                }
            }
            const importData = {
                issues: [],
                labels: {},
                users: {},
            };
            for (const issue of issueData) {
                importData.issues.push({
                    title: issue.title,
                    description: `${issue.body}\n\n[View original issue in GitHub](${issue.url})`,
                    url: issue.url,
                    comments: issue.comments.nodes
                        ? issue.comments.nodes
                            .filter(comment => comment.author.id)
                            .map(comment => ({
                            body: comment.body,
                            userId: comment.author.id,
                            createdAt: new Date(comment.createdAt),
                        }))
                        : [],
                    labels: issue.labels.nodes ? issue.labels.nodes.map(label => label.id) : [],
                    createdAt: new Date(issue.createdAt),
                });
                const users = issue.comments.nodes
                    ? issue.comments.nodes.map(comment => ({
                        id: comment.author.id,
                        name: comment.author.login,
                        avatarUrl: comment.author.avatarUrl,
                        email: comment.author.email,
                    }))
                    : [];
                for (const user of users) {
                    const { id, email } = user, userData = __rest(user, ["id", "email"]);
                    if (id) {
                        importData.users[id] = Object.assign(Object.assign({}, userData), { email: email && email.length > 0 ? email : undefined });
                    }
                }
                const labels = issue.labels.nodes
                    ? issue.labels.nodes.map(label => ({
                        id: label.id,
                        color: `#${label.color}`,
                        name: label.name,
                        description: label.description,
                    }))
                    : [];
                for (const label of labels) {
                    const { id } = label, labelData = __rest(label, ["id"]);
                    importData.labels[id] = labelData;
                }
            }
            return importData;
        });
        this.apiKey = apiKey;
        this.owner = owner;
        this.repo = repo;
    }
    get name() {
        return "GitHub";
    }
    get defaultTeamName() {
        return this.repo;
    }
}

const githubImport = () => __awaiter(void 0, void 0, void 0, function* () {
    const answers = yield inquirer__namespace.prompt(questions$5);
    const [owner, repo] = answers.repo.split("/");
    const githubImporter = new GithubImporter(answers.githubApiKey, owner, repo);
    return githubImporter;
});
const questions$5 = [
    {
        type: "input",
        name: "githubApiKey",
        message: "Input your personal GitHub access token (https://github.com/settings/tokens, select `repo` scope)",
    },
    {
        type: "input",
        name: "repo",
        message: 'From which repo do you want to import issues from (e.g. "facebook/react")',
    },
];

// eslint-disable-next-line @typescript-eslint/no-var-requires
const j2m$1 = require("jira2md");
/**
 * Import issues from a Jira CSV export.
 *
 * @param filePath Path to CSV file
 * @param orgSlug Jira site name
 * @param customUrl Non-cloud Jira instance base URL
 */
class JiraCsvImporter {
    constructor(filePath, orgSlug, customUrl) {
        this.import = () => __awaiter(this, void 0, void 0, function* () {
            var _a;
            const data = (yield csv__default["default"]().fromFile(this.filePath));
            const importData = {
                issues: [],
                labels: {},
                users: {},
                statuses: {},
            };
            const statuses = Array.from(new Set(data.map(row => row.Status)));
            const assignees = Array.from(new Set(data.map(row => row.Assignee)));
            for (const user of assignees) {
                importData.users[user] = {
                    name: user,
                };
            }
            for (const status of statuses) {
                if ((_a = importData.statuses) === null || _a === void 0 ? void 0 : _a[status]) {
                    importData.statuses[status] = {
                        name: status,
                    };
                }
            }
            for (const row of data) {
                const url = this.jiraSiteName
                    ? `https://${this.jiraSiteName}.atlassian.net/browse/${row["Issue key"]}`
                    : `${this.customJiraUrl}/browse/${row["Issue key"]}`;
                const mdDesc = row.Description ? j2m$1.to_markdown(row.Description) : undefined;
                const description = mdDesc && url
                    ? `${mdDesc}\n\n[View original issue in Jira](${url})`
                    : url
                        ? `[View original issue in Jira](${url})`
                        : undefined;
                const priority = mapPriority$1(row.Priority);
                const type = `Type: ${row["Issue Type"]}`;
                const release = row.Release && row.Release.length > 0 ? `Release: ${row.Release}` : undefined;
                const assigneeId = row.Assignee && row.Assignee.length > 0 ? row.Assignee : undefined;
                const status = row.Status;
                const labels = [type];
                if (release) {
                    labels.push(release);
                }
                importData.issues.push({
                    title: row.Summary,
                    description,
                    status,
                    priority,
                    url,
                    assigneeId,
                    labels,
                });
                for (const lab of labels) {
                    if (!importData.labels[lab]) {
                        importData.labels[lab] = {
                            name: lab,
                        };
                    }
                }
            }
            return importData;
        });
        this.filePath = filePath;
        this.jiraSiteName = orgSlug;
        this.customJiraUrl = customUrl;
    }
    get name() {
        return "Jira (CSV)";
    }
    get defaultTeamName() {
        return "Jira";
    }
}
const mapPriority$1 = (input) => {
    const priorityMap = {
        Highest: 1,
        High: 2,
        Medium: 3,
        Low: 4,
        Lowest: 0,
    };
    return priorityMap[input] || 0;
};

const BASE_PATH$4 = process.cwd();
const JIRA_URL_REGEX = /^https?:\/\/(\S+).atlassian.net/;
const jiraCsvImport = () => __awaiter(void 0, void 0, void 0, function* () {
    var _a, _b;
    const answers = yield inquirer__namespace.prompt(questions$4);
    let orgSlug = "";
    if (answers.jiraUrlName) {
        orgSlug = (_b = (_a = answers.jiraUrlName.match(JIRA_URL_REGEX)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : "";
    }
    const jiraImporter = new JiraCsvImporter(answers.jiraFilePath, orgSlug, answers.customJiraUrl);
    return jiraImporter;
});
const questions$4 = [
    {
        basePath: BASE_PATH$4,
        type: "filePath",
        name: "jiraFilePath",
        message: "Select your exported CSV file of Jira issues",
    },
    {
        type: "confirm",
        name: "isCloud",
        message: "Is your Jira installation on Jira Cloud (url similar to https://acme.atlassian.net)?",
        default: true,
    },
    {
        type: "input",
        name: "customJiraUrl",
        message: "Input the URL of your on-prem Jira installation (e.g. https://jira.mydomain.com):",
        when: (answers) => {
            return !answers.isCloud;
        },
        validate: (input) => {
            return input !== "";
        },
    },
    {
        type: "input",
        name: "jiraUrlName",
        message: "Input the URL of your Jira Cloud installation (e.g. https://acme.atlassian.net):",
        when: (answers) => {
            return answers.isCloud;
        },
        validate: (input) => {
            return !!input.match(JIRA_URL_REGEX);
        },
    },
];

/**
 * Import issues from Linear CSV export.
 *
 * @param filePath  path to csv file
 */
class LinearCsvImporter {
    constructor(filePath) {
        this.import = () => __awaiter(this, void 0, void 0, function* () {
            const data = (yield csv__default["default"]().fromFile(this.filePath));
            const importData = {
                issues: [],
                labels: {},
                users: {},
                statuses: {},
            };
            const assignees = Array.from(new Set(data.map(row => row.Assignee)));
            for (const user of assignees) {
                importData.users[user] = {
                    name: user,
                };
            }
            for (const row of data) {
                if (!!row.Archived) {
                    continue;
                }
                const tags = row.Labels.split(", ");
                const labels = tags.filter(tag => !!tag);
                importData.issues.push({
                    title: row.Title,
                    description: row.Description,
                    priority: mapPriority(row.Priority),
                    status: row.Status,
                    assigneeId: row.Assignee,
                    completedAt: !!row.Completed ? new Date(row.Completed) : undefined,
                    startedAt: !!row.Started ? new Date(row.Started) : undefined,
                    labels,
                });
                for (const lab of labels) {
                    if (!importData.labels[lab]) {
                        importData.labels[lab] = {
                            name: lab,
                        };
                    }
                }
            }
            return importData;
        });
        this.filePath = filePath;
    }
    get name() {
        return "Linear (CSV)";
    }
    get defaultTeamName() {
        return "Linear";
    }
}
const mapPriority = (input) => {
    const priorityMap = {
        "No priority": 0,
        Urgent: 1,
        High: 2,
        Medium: 3,
        Low: 4,
    };
    return priorityMap[input] || 0;
};

const BASE_PATH$3 = process.cwd();
const linearCsvImporter = () => __awaiter(void 0, void 0, void 0, function* () {
    const answers = yield inquirer__namespace.prompt(questions$3);
    const linearImporter = new LinearCsvImporter(answers.linearFilePath);
    return linearImporter;
});
const questions$3 = [
    {
        basePath: BASE_PATH$3,
        type: "filePath",
        name: "linearFilePath",
        message: "Select your exported CSV file of Linear issues",
    },
];

// eslint-disable-next-line @typescript-eslint/no-var-requires
const j2m = require("jira2md");
/**
 * Import issues from an Pivotal Tracker CSV export.
 *
 * @param filePath  path to csv file
 * @param orgSlug   base Pivotal project url
 */
class PivotalCsvImporter {
    constructor(filePath) {
        this.import = () => __awaiter(this, void 0, void 0, function* () {
            const data = (yield csv__default["default"]().fromFile(this.filePath));
            const importData = {
                issues: [],
                labels: {},
                users: {},
                statuses: {},
            };
            const assignees = Array.from(new Set(data.map(row => row["Owned By"])));
            for (const user of assignees) {
                importData.users[user] = {
                    name: user,
                };
            }
            for (const row of data) {
                const type = row.Type;
                if (type === "epic" || type === "release") {
                    continue;
                }
                const title = row.Title;
                if (!title) {
                    continue;
                }
                const url = row.URL;
                const mdDesc = j2m.to_markdown(row.Description);
                const description = url ? `${mdDesc}\n\n[View original issue in Pivotal](${url})` : mdDesc;
                // const priority = parseInt(row['Estimate']) || undefined;
                const tags = row.Labels.split(",");
                const assigneeId = row["Owned By"] && row["Owned By"].length > 0 ? row["Owned By"] : undefined;
                const status = !!row["Accepted at"] ? "Done" : "Todo";
                const labels = tags.filter(tag => !!tag);
                const createdAt = row["Created at"];
                importData.issues.push({
                    title,
                    description,
                    status,
                    url,
                    assigneeId,
                    labels,
                    createdAt,
                });
                for (const lab of labels) {
                    if (!importData.labels[lab]) {
                        importData.labels[lab] = {
                            name: lab,
                        };
                    }
                }
            }
            return importData;
        });
        this.filePath = filePath;
    }
    get name() {
        return "Pivotal (CSV)";
    }
    get defaultTeamName() {
        return "Pivotal";
    }
}

const BASE_PATH$2 = process.cwd();
const pivotalCsvImport = () => __awaiter(void 0, void 0, void 0, function* () {
    const answers = yield inquirer__namespace.prompt(questions$2);
    const pivotalImporter = new PivotalCsvImporter(answers.pivotalFilePath);
    return pivotalImporter;
});
const questions$2 = [
    {
        basePath: BASE_PATH$2,
        type: "filePath",
        name: "pivotalFilePath",
        message: "Select your exported CSV file of Pivotal stories",
    },
];

const parseBooleanColumn = (item) => item == "TRUE";
const parseStringArrayColumn = (item) => item.split(";").filter(s => s.length > 0);
const parseInt = (item) => Number.parseInt(item) || 0;
const parseDate = (item, _, __, row) => {
    if (item.length <= 0) {
        return null;
    }
    // Inoptimal method for finding the timezone UTC offset, we parse it from the UTC offset column in this row each time
    const utcOffset = row.find(c => /[+-]([01]\d|2[0-4])(:?[0-5]\d)?/g.test(c)) || "";
    return new Date(item + " " + utcOffset);
};
const colParser = {
    owners: parseStringArrayColumn,
    is_completed: parseBooleanColumn,
    created_at: parseDate,
    started_at: parseDate,
    updated_at: parseDate,
    moved_at: parseDate,
    completed_at: parseDate,
    estimate: parseInt,
    external_ticket_count: parseInt,
    external_tickets: parseStringArrayColumn,
    is_blocked: parseBooleanColumn,
    is_a_blocker: parseBooleanColumn,
    due_date: parseDate,
    labels: parseStringArrayColumn,
    epic_labels: parseStringArrayColumn,
    tasks: parseStringArrayColumn,
    is_archived: parseBooleanColumn,
    // we parse dates using it, so to avoid confusion, leave it out of parsed rows
    utc_offset: "omit",
};
/**
 * Import issues from an Shortcut CSV export.
 *
 * @param filePath  path to csv file
 * @param workspaceSlug   Shortcut workspace slug (https://app.shortcut.com/[THIS])
 * @param apiToken  A Shortcut API token (https://app.shortcut.com/settings/account/api-tokens)
 */
class ShortcutCsvImporter {
    constructor(filePath, workspaceSlug, apiToken) {
        this.filePath = filePath;
        this.apiToken = apiToken;
        this.import = () => __awaiter(this, void 0, void 0, function* () {
            const data = (yield csv__default["default"]({ colParser, checkType: true }).fromFile(this.filePath));
            const importData = {
                issues: [],
                labels: {},
                users: {},
                statuses: {},
                resourceURLSuffix: "?token=" + this.apiToken,
            };
            const assignees = Array.from(new Set(data.map(row => row.owners).flat()));
            for (const user of assignees) {
                importData.users[user] = {
                    name: user,
                    email: user,
                };
            }
            for (const row of data) {
                const title = row.name;
                if (!title) {
                    continue;
                }
                const url = this.shortcutBaseURL + "/story/" + row.id;
                const descriptionParts = [
                    row.description,
                    row.tasks.map(t => `- ${t}`).join("\n"),
                    row.external_tickets.map(externalUrl => `* **External Link:** ${externalUrl}`).join("\n"),
                    `[View original issue in Shortcut](${url})`,
                ];
                const description = descriptionParts.filter(s => s.length > 0).join("\n\n");
                const tags = row.labels;
                tags.push(row.type);
                const assigneeId = row.owners[0];
                const status = mapStatus(row.state);
                const labels = tags.filter(tag => !!tag);
                const createdAt = row.created_at;
                importData.issues.push({
                    title,
                    description,
                    status,
                    url,
                    assigneeId,
                    labels,
                    createdAt,
                });
                for (const lab of labels) {
                    if (!importData.labels[lab]) {
                        importData.labels[lab] = {
                            name: lab,
                        };
                    }
                }
            }
            return importData;
        });
        this.shortcutBaseURL = "https://app.shortcut.com/" + workspaceSlug;
    }
    get name() {
        return "Shortcut (CSV)";
    }
    get defaultTeamName() {
        return "Shortcut";
    }
}
const mapStatus = (input) => {
    const priorityMap = {
        // 'Standard' workflow template
        Unscheduled: "Backlog",
        "Ready for Development": "Todo",
        "In Development": "In Progress",
        "Ready for Review": "In Review",
        "Ready for Deploy": "Done",
        Completed: "Done",
        // 'Simple' workflow template
        "To Do": "Todo",
        Doing: "In Progress",
        Done: "Done",
    };
    return priorityMap[input] || "Todo";
};

const BASE_PATH$1 = process.cwd();
const shortcutCsvImport = () => __awaiter(void 0, void 0, void 0, function* () {
    const answers = yield inquirer__namespace.prompt(questions$1);
    const shortcutImporter = new ShortcutCsvImporter(answers.shortcutFilePath, answers.shortcutWorkspaceSlug, answers.shortcutAPIToken);
    return shortcutImporter;
});
const questions$1 = [
    {
        basePath: BASE_PATH$1,
        type: "filePath",
        name: "shortcutFilePath",
        message: "Select your exported CSV file of Shortcut stories",
    },
    {
        type: "input",
        name: "shortcutWorkspaceSlug",
        message: "Input the slug of your Shortcut workspace (e.g. acme):",
    },
    {
        type: "input",
        name: "shortcutAPIToken",
        message: "To transfer files from Shortcut, enter a Shortcut API token (https://app.shortcut.com/settings/account/api-tokens):",
    },
];

class TrelloJsonImporter {
    constructor(filePath, discardArchivedCards, discardArchivedLists) {
        this.import = () => __awaiter(this, void 0, void 0, function* () {
            const bytes = fs__default["default"].readFileSync(this.filePath);
            const data = JSON.parse(bytes);
            const importData = {
                issues: [],
                labels: {},
                users: {},
                statuses: {},
            };
            // Map card id => checklist so we can add them to the issues in the next step
            const checkLists = {};
            for (const checklist of data.checklists) {
                checkLists[checklist.idCard] = checklist;
            }
            // Map card id => comments so we can add them to the issues in the next step
            const comments = {};
            for (const action of data.actions) {
                if (action.type !== "commentCard") {
                    continue;
                }
                const { data: { text, card }, memberCreator, date, } = action;
                // Handle comment creator does not exist in import
                if (!memberCreator) {
                    continue;
                }
                importData.users[memberCreator.id] = { name: memberCreator.fullName, avatarUrl: memberCreator.avatarUrl };
                const importComment = { body: text, userId: memberCreator.id, createdAt: new Date(date) };
                if (card.id in comments) {
                    comments[card.id].push(importComment);
                }
                else {
                    comments[card.id] = [importComment];
                }
            }
            for (const card of data.cards) {
                const url = card.shortUrl;
                const mdDesc = card.desc;
                const checklist = checkLists[card.id];
                let formattedChecklist = "";
                if (checklist) {
                    formattedChecklist = checklist.checkItems
                        .sort((item1, item2) => item1.pos - item2.pos)
                        .map(item => `- [${item.state === "complete" ? "x" : " "}] ${item.name}`)
                        .join("\n");
                }
                const formattedAttachments = card.attachments
                    .map(attachment => `[${attachment.name}](${attachment.url})`)
                    .join("\n");
                const description = `${mdDesc}${formattedChecklist && `\n${formattedChecklist}`}${formattedAttachments && `\n\nAttachments:\n${formattedAttachments}`}\n\n[View original card in Trello](${url})`;
                const labels = card.labels.map(l => l.id);
                if (this.discardArchivedCards && card.closed) {
                    continue;
                }
                if (this.discardArchivedLists &&
                    data.lists.find(list => list.id === card.idList && list.closed)) {
                    continue;
                }
                importData.issues.push({
                    title: card.name,
                    description,
                    url,
                    labels,
                    comments: comments[card.id],
                });
                const allLabels = card.labels.map(label => ({
                    id: label.id,
                    color: mapLabelColor(label.color),
                    // Trello allows labels with no name and only a color value, but we must specify a name
                    name: label.name || `Trello-${label.color}`,
                }));
                for (const label of allLabels) {
                    const { id } = label, labelData = __rest(label, ["id"]);
                    importData.labels[id] = labelData;
                }
            }
            return importData;
        });
        this.filePath = filePath;
        this.discardArchivedCards = discardArchivedCards;
        this.discardArchivedLists = discardArchivedLists;
    }
    get name() {
        return "Trello (JSON)";
    }
    get defaultTeamName() {
        return "Trello";
    }
}
// Maps Trello colors to Linear branded colors
const mapLabelColor = (color) => {
    const colorMap = {
        green: "#0F783C",
        yellow: "#F2C94C",
        orange: "#DB6E1F",
        red: "#C52828",
        purple: "#5E6AD2",
        blue: "#0F7488",
        sky: "#26B5CE",
        lime: "#4CB782",
        pink: "#EB5757",
        black: "#ffffff", // black is the new white ¯\_(ツ)_/¯
    };
    return colorMap[color];
};

const BASE_PATH = process.cwd();
const trelloJsonImport = () => __awaiter(void 0, void 0, void 0, function* () {
    const answers = yield inquirer__namespace.prompt(questions);
    const trelloImporter = new TrelloJsonImporter(answers.trelloFilePath, answers.discardArchivedCards, answers.discardArchivedLists);
    return trelloImporter;
});
const questions = [
    {
        basePath: BASE_PATH,
        type: "filePath",
        name: "trelloFilePath",
        message: "Select your exported JSON file of Trello cards",
    },
    {
        type: "confirm",
        name: "discardArchivedCards",
        message: "Would you like to discard the archived cards?",
        default: true,
    },
    {
        type: "confirm",
        name: "discardArchivedLists",
        message: "Would you like to discard the (possibly unarchived) cards within archived lists?",
        default: true,
    },
];

/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/**
 * String replace with async function.
 */
const replaceAsync = (str, regex, asyncFn) => __awaiter(void 0, void 0, void 0, function* () {
    const promises = [];
    // @ts-ignore
    str.replace(regex, (match, ...args) => {
        // @ts-ignore
        const promise = asyncFn(match, ...args);
        promises.push(promise);
    });
    const data = yield Promise.all(promises);
    return str.replace(regex, () => data.shift());
});

const IMAGE_MD_REGEX = /(?:!\[(.*?)\]\((https?:\/\/.*?)\))/;
const IMAGE_TAG_REGEX = /(?:<img.*?src=\"(.*?)\".*?>)/;
/**
 * Replace markdown image URLs with Linear uploaded ones.
 *
 * Also supports image tags used by GitHub.
 *
 * @param client Linear API client.
 * @param text Markdown content.
 * @param urlSuffix A suffix to append to each image URL, such as to perform authentication
 * @returns Markdown content with images using the new Linear URLs
 */
const replaceImagesInMarkdown = (client, text, urlSuffix) => __awaiter(void 0, void 0, void 0, function* () {
    let result = text;
    const effectiveURLSuffix = urlSuffix || "";
    // Markdown tags
    result = yield replaceAsync(result, IMAGE_MD_REGEX, (match, ...args) => __awaiter(void 0, void 0, void 0, function* () {
        const title = args[0];
        const url = args[1];
        const uploadedUrl = yield replaceImageUrl(client, url + effectiveURLSuffix);
        return `![${title}](${uploadedUrl})`;
    }));
    // HTML tags
    result = yield replaceAsync(result, IMAGE_TAG_REGEX, (match, ...args) => __awaiter(void 0, void 0, void 0, function* () {
        const url = args[0];
        const uploadedUrl = yield replaceImageUrl(client, url + effectiveURLSuffix);
        return `![](${uploadedUrl})`;
    }));
    return result;
});
/**
 * Downloads image and upload it to
 *
 * @param client Linear API client
 * @param url URL of the source image
 * @returns URL of the uploaded image
 */
const replaceImageUrl = (client, url) => __awaiter(void 0, void 0, void 0, function* () {
    try {
        const res = yield client.imageUploadFromUrl(url);
        if (res === null || res === void 0 ? void 0 : res.url) {
            return res === null || res === void 0 ? void 0 : res.url;
        }
    }
    catch (err) {
        console.error(`Failed to replace image`, err.message);
    }
    return url;
});

const defaultStateColors = {
    backlog: "#bec2c8",
    started: "#f2c94c",
    completed: "#5e6ad2",
};
/**
 * Import issues into Linear via the API.
 */
const importIssues = (apiKey, importer) => __awaiter(void 0, void 0, void 0, function* () {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
    const client = new sdk.LinearClient({ apiKey });
    const importData = yield importer.import();
    const teamsQuery = yield client.teams();
    const viewerQuery = yield client.viewer;
    const usersQuery = yield client.users();
    const teams = (_a = teamsQuery === null || teamsQuery === void 0 ? void 0 : teamsQuery.nodes) !== null && _a !== void 0 ? _a : [];
    const users = (_c = (_b = usersQuery === null || usersQuery === void 0 ? void 0 : usersQuery.nodes) === null || _b === void 0 ? void 0 : _b.filter(user => user.active)) !== null && _c !== void 0 ? _c : [];
    const viewer = viewerQuery === null || viewerQuery === void 0 ? void 0 : viewerQuery.id;
    // Prompt the user to either get or create a team
    const importAnswers = yield inquirer__namespace.prompt([
        {
            type: "confirm",
            name: "newTeam",
            message: "Do you want to create a new team for imported issues?",
            default: true,
        },
        {
            type: "input",
            name: "teamName",
            message: "Name of the team:",
            default: importer.defaultTeamName || importer.name,
            when: (answers) => {
                return answers.newTeam;
            },
        },
        {
            type: "list",
            name: "targetTeamId",
            message: "Import into team:",
            choices: () => __awaiter(void 0, void 0, void 0, function* () {
                return teams.map(team => ({
                    name: `[${team.key}] ${team.name}`,
                    value: team.id,
                }));
            }),
            when: (answers) => {
                return !answers.newTeam;
            },
        },
        {
            type: "confirm",
            name: "includeProject",
            message: "Do you want to import to a specific project?",
            when: (answers) => __awaiter(void 0, void 0, void 0, function* () {
                var _q;
                // if no team is selected then don't show projects screen
                if (!answers.targetTeamId) {
                    return false;
                }
                const team = yield client.team(answers.targetTeamId);
                const teamProjects = yield (team === null || team === void 0 ? void 0 : team.projects());
                const projects = (_q = teamProjects === null || teamProjects === void 0 ? void 0 : teamProjects.nodes) !== null && _q !== void 0 ? _q : [];
                return projects.length > 0;
            }),
        },
        {
            type: "list",
            name: "targetProjectId",
            message: "Import into project:",
            choices: (answers) => __awaiter(void 0, void 0, void 0, function* () {
                var _r;
                // if no team is selected then don't show projects screen
                if (!answers.targetTeamId) {
                    return false;
                }
                const team = yield client.team(answers.targetTeamId);
                const teamProjects = yield (team === null || team === void 0 ? void 0 : team.projects());
                const projects = (_r = teamProjects === null || teamProjects === void 0 ? void 0 : teamProjects.nodes) !== null && _r !== void 0 ? _r : [];
                return projects.map(project => ({
                    name: project.name,
                    value: project.id,
                }));
            }),
            when: (answers) => {
                return answers.includeProject;
            },
        },
        {
            type: "confirm",
            name: "includeComments",
            message: "Do you want to include comments in the issue description?",
            when: () => {
                return !!importData.issues.find(issue => issue.comments && issue.comments.length > 0);
            },
        },
        {
            type: "confirm",
            name: "selfAssign",
            message: "Do you want to assign these issues to yourself?",
            default: true,
        },
        {
            type: "list",
            name: "targetAssignee",
            message: "Assign to user:",
            choices: () => {
                const map = users.map(user => ({
                    name: user.name,
                    value: user.id,
                }));
                map.unshift({ name: "[Unassigned]", value: "" });
                map.unshift({ name: "[Provided assignee]", value: "{{assignee}}" });
                return map;
            },
            when: (answers) => {
                return !answers.selfAssign;
            },
        },
    ]);
    let teamKey;
    let teamId;
    if (importAnswers.newTeam) {
        // Create a new team
        const teamResponse = yield client.createTeam({
            name: importAnswers.teamName,
        });
        const team = yield (teamResponse === null || teamResponse === void 0 ? void 0 : teamResponse.team);
        teamKey = team === null || team === void 0 ? void 0 : team.key;
        teamId = team === null || team === void 0 ? void 0 : team.id;
    }
    else {
        // Use existing team
        const existingTeam = teams === null || teams === void 0 ? void 0 : teams.find(team => team.id === importAnswers.targetTeamId);
        teamKey = existingTeam === null || existingTeam === void 0 ? void 0 : existingTeam.key;
        teamId = importAnswers.targetTeamId;
    }
    if (!teamId) {
        throw new Error("No team id found");
    }
    const teamInfo = yield client.team(teamId);
    const existingLabels = [];
    let teamLabels = yield (teamInfo === null || teamInfo === void 0 ? void 0 : teamInfo.labels());
    existingLabels.push(...((_d = teamLabels === null || teamLabels === void 0 ? void 0 : teamLabels.nodes) !== null && _d !== void 0 ? _d : []));
    while ((_e = teamLabels === null || teamLabels === void 0 ? void 0 : teamLabels.pageInfo) === null || _e === void 0 ? void 0 : _e.hasNextPage) {
        teamLabels = yield teamLabels.fetchNext();
        existingLabels.push(...((_f = teamLabels === null || teamLabels === void 0 ? void 0 : teamLabels.nodes) !== null && _f !== void 0 ? _f : []));
    }
    const organization = yield client.organization;
    let workspaceLabels = yield organization.labels();
    existingLabels.push(...((_g = workspaceLabels === null || workspaceLabels === void 0 ? void 0 : workspaceLabels.nodes) !== null && _g !== void 0 ? _g : []));
    while ((_h = workspaceLabels === null || workspaceLabels === void 0 ? void 0 : workspaceLabels.pageInfo) === null || _h === void 0 ? void 0 : _h.hasNextPage) {
        workspaceLabels = yield workspaceLabels.fetchNext();
        existingLabels.push(...((_j = workspaceLabels === null || workspaceLabels === void 0 ? void 0 : workspaceLabels.nodes) !== null && _j !== void 0 ? _j : []));
    }
    const workflowStates = yield (teamInfo === null || teamInfo === void 0 ? void 0 : teamInfo.states());
    const existingLabelMap = {};
    const existingLabelGroupsMap = {};
    for (const label of existingLabels) {
        const labelName = (_k = label.name) === null || _k === void 0 ? void 0 : _k.toLowerCase();
        const children = yield label.children();
        if (((_l = children === null || children === void 0 ? void 0 : children.nodes) === null || _l === void 0 ? void 0 : _l.length) > 0) {
            if (labelName && label.id && !existingLabelGroupsMap[labelName]) {
                existingLabelGroupsMap[labelName] = label.id;
            }
        }
        else {
            if (labelName && label.id && !existingLabelMap[labelName]) {
                existingLabelMap[labelName] = label.id;
            }
        }
    }
    const projectId = importAnswers.targetProjectId;
    // Create labels and mapping to source data
    const labelMapping = {};
    for (const labelId of Object.keys(importData.labels)) {
        const label = importData.labels[labelId];
        let labelName = ___default["default"].truncate(label.name.trim(), { length: 20 });
        // Check if this label matches with an existing group label
        let actualLabelId = existingLabelGroupsMap[labelName.toLowerCase()];
        if (actualLabelId) {
            // This label has matched with an existing group label. We cannot re-use the label as-is, it will be renamed.
            actualLabelId = undefined;
            labelName = `${labelName} (imported)`;
        }
        // Check if this label matches with an existing label
        actualLabelId = existingLabelMap[labelName.toLowerCase()];
        if (!actualLabelId) {
            const labelResponse = yield client.createIssueLabel({
                name: labelName,
                description: label.description,
                color: label.color,
                teamId,
            });
            const issueLabel = yield (labelResponse === null || labelResponse === void 0 ? void 0 : labelResponse.issueLabel);
            if (issueLabel === null || issueLabel === void 0 ? void 0 : issueLabel.id) {
                actualLabelId = issueLabel === null || issueLabel === void 0 ? void 0 : issueLabel.id;
            }
            existingLabelMap[labelName.toLowerCase()] = actualLabelId;
        }
        labelMapping[labelId] = actualLabelId;
    }
    const existingStateMap = {};
    for (const state of (_m = workflowStates === null || workflowStates === void 0 ? void 0 : workflowStates.nodes) !== null && _m !== void 0 ? _m : []) {
        const stateName = (_o = state.name) === null || _o === void 0 ? void 0 : _o.toLowerCase();
        if (stateName && state.id && !existingStateMap[stateName]) {
            existingStateMap[stateName] = state.id;
        }
    }
    const existingUserMap = {};
    for (const user of users) {
        const userName = (_p = user.name) === null || _p === void 0 ? void 0 : _p.toLowerCase();
        if (userName && user.id && !existingUserMap[userName]) {
            existingUserMap[userName] = user.id;
        }
    }
    // Create issues
    for (const issue of importData.issues) {
        const issueDescription = issue.description
            ? yield replaceImagesInMarkdown(client, issue.description, importData.resourceURLSuffix)
            : undefined;
        const description = importAnswers.includeComments && issue.comments
            ? yield buildComments(client, issueDescription || "", issue.comments, importData)
            : issueDescription;
        const labelIds = issue.labels ? _.uniq(issue.labels.map(labelId => labelMapping[labelId])) : undefined;
        let stateId = !!issue.status ? existingStateMap[issue.status.toLowerCase()] : undefined;
        // Create a new state since one doesn't already exist with this name
        if (!stateId && issue.status) {
            let stateType = "backlog";
            if (issue.completedAt) {
                stateType = "completed";
            }
            else if (issue.startedAt) {
                stateType = "started";
            }
            const newStateResult = yield client.createWorkflowState({
                name: issue.status,
                teamId,
                color: defaultStateColors[stateType],
                type: stateType,
            });
            if (newStateResult === null || newStateResult === void 0 ? void 0 : newStateResult.success) {
                const newState = yield newStateResult.workflowState;
                if (newState === null || newState === void 0 ? void 0 : newState.id) {
                    existingStateMap[issue.status.toLowerCase()] = newState.id;
                    stateId = newState.id;
                }
            }
        }
        const existingAssigneeId = !!issue.assigneeId
            ? existingUserMap[issue.assigneeId.toLowerCase()]
            : undefined;
        let assigneeId;
        if (importAnswers.selfAssign) {
            assigneeId = viewer;
        }
        else if (importAnswers.targetAssignee === "{{assignee}}") {
            assigneeId = existingAssigneeId;
        }
        else {
            assigneeId = importAnswers.targetAssignee || undefined;
        }
        const formattedDueDate = issue.dueDate ? dateFns.format(issue.dueDate, "yyyy-MM-dd") : undefined;
        yield client.createIssue({
            teamId,
            projectId: projectId,
            title: issue.title,
            description,
            priority: issue.priority,
            labelIds,
            stateId,
            assigneeId,
            dueDate: formattedDueDate,
        });
    }
    console.info(chalk__default["default"].green(`${importer.name} issues imported to your team: https://linear.app/team/${teamKey}/all`));
});
// Build comments into issue description
const buildComments = (client, description, comments, importData) => __awaiter(void 0, void 0, void 0, function* () {
    const newComments = [];
    for (const comment of comments) {
        const user = importData.users[comment.userId];
        const date = comment.createdAt ? comment.createdAt.toISOString().split("T")[0] : undefined;
        const body = yield replaceImagesInMarkdown(client, comment.body || "", importData.resourceURLSuffix);
        newComments.push(`**${user.name}**${" " + date}\n\n${body}\n`);
    }
    return `${description}\n\n---\n\n${newComments.join("\n\n")}`;
});

inquirer__namespace.registerPrompt("filePath", require("inquirer-file-path"));
(() => __awaiter(void 0, void 0, void 0, function* () {
    try {
        const importAnswers = yield inquirer__namespace.prompt([
            {
                type: "input",
                name: "linearApiKey",
                message: "Input your Linear API key (https://linear.app/settings/api)",
            },
            {
                type: "list",
                name: "service",
                message: "Which service would you like to import from?",
                choices: [
                    {
                        name: "GitHub",
                        value: "github",
                    },
                    {
                        name: "Jira (CSV export)",
                        value: "jiraCsv",
                    },
                    {
                        name: "Asana (CSV export)",
                        value: "asanaCsv",
                    },
                    {
                        name: "Pivotal (CSV export)",
                        value: "pivotalCsv",
                    },
                    {
                        name: "Shortcut (CSV export)",
                        value: "shortcutCsv",
                    },
                    {
                        name: "Trello (JSON export)",
                        value: "trelloJson",
                    },
                    {
                        name: "Linear (CSV export)",
                        value: "linearCsv",
                    },
                ],
            },
        ]);
        // TODO: Validate Linear API
        let importer;
        switch (importAnswers.service) {
            case "github":
                importer = yield githubImport();
                break;
            case "jiraCsv":
                importer = yield jiraCsvImport();
                break;
            case "asanaCsv":
                importer = yield asanaCsvImport();
                break;
            case "pivotalCsv":
                importer = yield pivotalCsvImport();
                break;
            case "shortcutCsv":
                importer = yield shortcutCsvImport();
                break;
            case "trelloJson":
                importer = yield trelloJsonImport();
                break;
            case "linearCsv":
                importer = yield linearCsvImporter();
                break;
            default:
                console.log(chalk__default["default"].red(`Invalid importer`));
                return;
        }
        if (importer) {
            yield importIssues(importAnswers.linearApiKey, importer);
        }
    }
    catch (e) {
        // Deal with the fact the chain failed
        console.error(e);
    }
}))();
//# sourceMappingURL=cli.js.map
