name: Draft stale PRs

on:
  schedule:
    - cron: "0 0 * * *" # daily at midnight UTC
  workflow_dispatch:

permissions:
  contents: read
  pull-requests: write
  issues: write

jobs:
  draft-stale-prs:
    runs-on: ubuntu-latest
    environment: ai-bots
    steps:
      - name: Create GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v2
        with:
          app-id: ${{ vars.DYAD_GITHUB_APP_ID }}
          private-key: ${{ secrets.DYAD_GITHUB_APP_PRIVATE_KEY }}
          permission-contents: read
          permission-pull-requests: write
          permission-issues: write

      - uses: actions/github-script@v7
        with:
          github-token: ${{ steps.app-token.outputs.token }}
          script: |
            const sevenDaysAgo = new Date();
            sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

            const allPrs = await github.paginate(github.rest.pulls.list, {
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'open',
              sort: 'updated',
              direction: 'asc',
            });
            // Pre-filter to non-draft, stale PRs and cap to avoid rate limit exhaustion
            const prs = allPrs.filter(pr => !pr.draft && new Date(pr.updated_at) < sevenDaysAgo).slice(0, 30);

            for (const pr of prs) {

              try {
                // Check the latest commit date using the PR head SHA
                const headCommit = await github.rest.git.getCommit({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  commit_sha: pr.head.sha,
                });
                const lastCommitDate = new Date(headCommit.data.committer.date);

                // Check comments (issue comments + review comments)
                // Note: issues.listComments doesn't support sort/direction params,
                // returns in ascending order. Fetch all and filter out bot comments.
                const allComments = await github.paginate(github.rest.issues.listComments, {
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                });
                const humanComments = allComments.filter(
                  c => !c.user?.login?.endsWith('[bot]')
                );
                const lastCommentDate = humanComments.length > 0
                  ? new Date(humanComments[humanComments.length - 1].created_at)
                  : new Date(0);

                const allReviewComments = await github.paginate(github.rest.pulls.listReviewComments, {
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  pull_number: pr.number,
                });
                const lastReviewCommentDate = allReviewComments.length > 0
                  ? new Date(allReviewComments[allReviewComments.length - 1].created_at)
                  : new Date(0);

                // Check timeline events for re-opens and ready_for_review
                // Note: must use listEventsForTimeline (not listEvents) because
                // ready_for_review is only available through the Timeline Events API.
                const events = await github.paginate(github.rest.issues.listEventsForTimeline, {
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                  per_page: 100,
                });
                const lastReopenOrReadyDate = events
                  .filter(e => e.event === 'reopened' || e.event === 'ready_for_review')
                  .reduce((latest, e) => {
                    const d = new Date(e.created_at);
                    return d > latest ? d : latest;
                  }, new Date(0));

                // Check review submissions (approvals, request-changes, dismissals)
                const reviews = await github.paginate(github.rest.pulls.listReviews, {
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  pull_number: pr.number,
                });
                const lastReviewDate = reviews.length > 0
                  ? new Date(reviews[reviews.length - 1].submitted_at)
                  : new Date(0);

                const lastActivity = new Date(Math.max(
                  lastCommitDate.getTime(),
                  lastCommentDate.getTime(),
                  lastReviewCommentDate.getTime(),
                  lastReopenOrReadyDate.getTime(),
                  lastReviewDate.getTime(),
                ));

                if (lastActivity < sevenDaysAgo) {
                  // Convert to draft using GraphQL (REST API doesn't support this)
                  await github.graphql(`
                    mutation($pullRequestId: ID!) {
                      convertPullRequestToDraft(input: { pullRequestId: $pullRequestId }) {
                        pullRequest { id }
                      }
                    }
                  `, { pullRequestId: pr.node_id });

                  await github.rest.issues.createComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    issue_number: pr.number,
                    body: "We flipped this PR to draft since there hasn't been any work in the last 7 days. Please mark it as ready for review when ready!",
                  });

                  console.log(`Converted PR #${pr.number} to draft: ${pr.title} (last activity: ${lastActivity.toISOString()})`);
                }
              } catch (error) {
                core.warning(`Failed to process PR #${pr.number}: ${error.message}`);
                if (error.status === 403 || error.status === 429) {
                  throw error;
                }
              }
            }
