name: Label issues in release

on:
    workflow_dispatch:
        inputs:
            tag:
                description: 'Release tag'
                required: true
                type: string
            label_name:
                description: 'Name of the label'
                required: true
                type: string
            label_description:
                description: 'Description of the label'
                default: ''
                required: false
                type: string
            label_color:
                description: 'A color of the added label'
                default: 'FFFFFF'
                required: false
                type: string
    workflow_call:
        inputs:
            tag:
                description: 'Release tag'
                required: true
                type: string
            label_name:
                description: 'Name of the label'
                required: true
                type: string
            label_description:
                description: 'Description of the label'
                default: ''
                required: false
                type: string
            label_color:
                description: 'A color of the added label'
                default: 'FFFFFF'
                required: false
                type: string
        outputs:
            issues:
                description: "JSON encoded list of issues linked to commits in the release"
                value: ${{ jobs.run.outputs.issues }}

# concurrency:
#   group: ${{ github.workflow }}__${{ github.job }}__${{ github.ref }}
#   cancel-in-progress: true

jobs:
    run:
        name: Run
        runs-on: ubuntu-latest
        outputs:
            issues: ${{ steps.linked_issues.outputs.result }}
        steps:
            -   name: Getting tags of the two latestest releases
                id: tags
                uses: actions/github-script@v6
                env:
                    TAG: ${{ inputs.tag }}
                with:
                    script: |
                        const { repository: { releases: { nodes: releases } } } = await github.graphql(`
                            query ($owner: String!, $repo: String!) {
                                repository(owner: $owner, name: $repo) {
                                    releases(first: 10, orderBy: { field: CREATED_AT, direction: DESC }) {
                                        nodes {
                                            name
                                            tagName
                                            tagCommit {
                                                oid
                                            }
                                            isPrerelease
                                            isDraft
                                            publishedAt
                                        }
                                    }
                                }
                            }
                            `, 
                            {
                                owner: context.repo.owner,
                                repo: context.repo.repo,
                            }
                        );

                        if (releases[0].tagName !== process.env.TAG) {
                            core.info(`Current latest tag: ${ releases[0].tagName }`);
                            core.setFailed(`Release with tag ${ process.env.TAG } is not latest one.`);
                            return;
                        }

                        const latestTag = process.env.TAG;
                        const [ { tagName: previousTag } ] = releases
                            .slice(1)
                            .filter(({ isDraft }) => !isDraft);

                        core.info('Found following tags:');
                        core.info(`    latest:        ${ latestTag }`);
                        core.info(`    second latest: ${ previousTag }`);

                        core.setOutput('latest', latestTag);
                        core.setOutput('previous', previousTag);
            
            -   name: Looking for commits between two releases
                id: commits
                uses: actions/github-script@v6
                env:
                    PREVIOUS_TAG: ${{ steps.tags.outputs.previous }}
                    LATEST_TAG: ${{ steps.tags.outputs.latest }}
                with:
                    script: |
                        const { data: { commits: commitsInRelease } } = await github.request('GET /repos/{owner}/{repo}/compare/{basehead}', {
                            owner: context.repo.owner,
                            repo: context.repo.repo,
                            basehead: `${ process.env.PREVIOUS_TAG }...${ process.env.LATEST_TAG }`,
                        });

                        if (commitsInRelease.length === 0) {
                            core.notice(`No commits found between ${ process.env.PREVIOUS_TAG } and ${ process.env.LATEST_TAG }`);
                            return [];
                        }

                        const commits = commitsInRelease.map(({ sha }) => sha);

                        core.startGroup(`Found ${ commits.length } commits`);
                        commits.forEach((sha) => {
                            core.info(sha);
                        })
                        core.endGroup();

                        return commits;

            -   name: Looking for issues linked to commits
                id: linked_issues
                uses: actions/github-script@v6
                env:
                    COMMITS: ${{ steps.commits.outputs.result }}
                with:
                    script: |
                        const commits = JSON.parse(process.env.COMMITS);

                        if (commits.length === 0) {
                            return [];
                        }

                        const map = {};

                        core.startGroup(`Looking for linked issues`);
                        for (const sha of commits) {
                            const result = await getLinkedIssuesForCommitPR(sha);
                            result.forEach((issue) => {
                                map[issue] = issue;
                            });
                        }
                        core.endGroup();

                        const issues = Object.values(map);
                    
                        if (issues.length > 0) {
                            core.startGroup(`Found ${ issues.length } unique issues`);
                            issues.sort().forEach((issue) => {
                                core.info(`#${ issue } - https://github.com/${ context.repo.owner }/${ context.repo.repo }/issues/${ issue }`);
                            })
                            core.endGroup();
                        } else {
                            core.notice('No linked issues found.');
                        }
                    
                        return issues;

                        async function getLinkedIssuesForCommitPR(sha) {
                            core.info(`Fetching issues for commit: ${ sha }`);

                            const response = await github.graphql(`
                                query ($owner: String!, $repo: String!, $sha: GitObjectID!) {
                                    repository(owner: $owner, name: $repo) {
                                    object(oid: $sha) {
                                        ... on Commit {
                                        associatedPullRequests(first: 10) {
                                            nodes {
                                            number
                                            title
                                            state
                                            merged
                                            closingIssuesReferences(first: 10) {
                                                nodes {
                                                number
                                                title
                                                closed
                                                }
                                            }
                                            }
                                        }
                                        }
                                    }
                                    }
                                }
                                `, {
                                owner: context.repo.owner,
                                repo: context.repo.repo,
                                sha,
                            });

                            if (!response) {
                                core.info('Nothing has found.');
                                return [];
                            }

                            const { repository: { object: { associatedPullRequests } } } = response;

                            const issues = associatedPullRequests
                                .nodes
                                .map(({ closingIssuesReferences: { nodes: issues } }) => issues.map(({ number }) => number))
                                .flat();

                            core.info(`Found following issues: ${ issues.join(', ') || '-' }\n`);

                            return issues;
                        }

            -   name: Creating label
                id: label_creating
                uses: actions/github-script@v6
                env:
                    LABEL_NAME: ${{ inputs.label_name }}
                    LABEL_COLOR: ${{ inputs.label_color }}
                    LABEL_DESCRIPTION: ${{ inputs.label_description }}
                with:
                    script: |
                        try {
                            const result = await github.request('POST /repos/{owner}/{repo}/labels', {
                                owner: context.repo.owner,
                                repo: context.repo.repo,
                                name: process.env.LABEL_NAME,
                                color: process.env.LABEL_COLOR,
                                description: process.env.LABEL_DESCRIPTION,
                            });

                            core.info('Label was created.');
                        } catch (error) {
                            if (error.status === 422) {
                                core.info('Label already exist.');
                            } else {
                                core.setFailed(error.message);
                            }
                        }
                        

            -   name: Adding label to issues
                id: labeling_issues
                uses: actions/github-script@v6
                env:
                    LABEL_NAME: ${{ inputs.label_name }}
                    ISSUES: ${{ steps.linked_issues.outputs.result }}
                with:
                    script: |
                        const issues = JSON.parse(process.env.ISSUES);

                        if (issues.length === 0) {
                            core.notice('No issues has found. Nothing to label.');
                            return;
                        }

                        for (const issue of issues) {
                            core.info(`Adding label to the issue #${ issue }...`);
                            await addLabelToIssue(issue, process.env.LABEL_NAME);
                            core.info('Done.\n');
                        }

                        async function addLabelToIssue(issue, label) {
                            return await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/labels', {
                                owner: context.repo.owner,
                                repo: context.repo.repo,
                                issue_number: issue,
                                labels: [ label ],
                            });
                        }
