name: '🏷️ Issue Triage'

on:
  issues:
    types:
      - 'opened'
      - 'reopened'
  issue_comment:
    types:
      - 'created'
  workflow_dispatch:
    inputs:
      issue_number:
        description: 'Issue number to triage'
        required: true
        type: 'number'

concurrency:
  group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.inputs.issue_number }}'
  cancel-in-progress: true

permissions:
  contents: 'read'
  issues: 'write'

jobs:
  triage-issue:
    if: |
      github.event_name == 'workflow_dispatch' ||
      github.event_name == 'issues' ||
      (
        github.event_name == 'issue_comment' &&
        contains(github.event.comment.body, '/triage') &&
        (
          github.event.comment.author_association == 'OWNER' ||
          github.event.comment.author_association == 'MEMBER' ||
          github.event.comment.author_association == 'COLLABORATOR'
        )
      )
    timeout-minutes: 30
    runs-on: self-hosted

    steps:
      - name: 'Check if already triaged'
        id: 'check_labels'
        if: github.event_name != 'workflow_dispatch'
        uses: 'actions/github-script@v7'
        with:
          script: |
            const labels = context.payload.issue?.labels?.map(l => l.name) || [];
            const triageLabels = ['bug', 'enhancement', 'documentation', 'question', 'invalid'];
            const areaLabels = labels.filter(l => l.startsWith('area/'));
            const hasTriageLabel = labels.some(l => triageLabels.includes(l)) || areaLabels.length > 0;
            const isRetriage = context.payload.comment?.body?.includes('/triage');
            
            if (hasTriageLabel && !labels.includes('needs-triage') && !isRetriage) {
              core.info(`Issue already triaged: ${labels.join(', ')}. Skipping.`);
              core.setOutput('skip', 'true');
            } else {
              core.setOutput('skip', 'false');
            }

      - name: 'Get issue data'
        id: 'get_issue'
        if: steps.check_labels.outputs.skip != 'true'
        uses: 'actions/github-script@v7'
        with:
          script: |
            const issueNumber = context.payload.inputs?.issue_number || context.issue?.number;
            core.setOutput('number', issueNumber);

      - name: 'Run Opencode Triage'
        id: 'opencode_triage'
        if: steps.check_labels.outputs.skip != 'true'
        run: |
          echo "Running Opencode Triage on issue ${{ steps.get_issue.outputs.number }}..."

          # Move to the repository
          cd /home/admin/coding/opencode-antigravity-auth
          
          # Run CLI with the agent config
          OUTPUT=$(opencode run --agent triage-bot "Triage issue ${{ steps.get_issue.outputs.number }}")
          
          echo "Opencode Output: $OUTPUT"
          
          echo "result<<EOF" >> $GITHUB_OUTPUT
          echo "$OUTPUT" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: 'Apply Labels and Respond'
        if: steps.check_labels.outputs.skip != 'true'
        uses: 'actions/github-script@v7'
        env:
          ISSUE_NUMBER: '${{ steps.get_issue.outputs.number }}'
          OPENCODE_OUTPUT: '${{ steps.opencode_triage.outputs.result }}'
        with:
          script: |
            const rawOutput = process.env.OPENCODE_OUTPUT;
            if (!rawOutput) {
              core.warning('No triage output available');
              return;
            }
            
            core.info(`Triage output: ${rawOutput}`);
            
            let parsed;
            try {
              parsed = JSON.parse(rawOutput.trim());
            } catch (e) {
              // Try to extract JSON from code blocks first
              const codeBlockMatch = rawOutput.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
              
              // Find the LAST JSON object containing triage fields (avoid matching code snippets)
              // Match JSON objects that contain "type_label" to ensure we get the triage output
              const triageJsonMatch = rawOutput.match(/\{"type_label"[\s\S]*?\}(?=\s*$|\s*```|\s*\n\n)/);
              
              // Fallback: find all potential JSON objects and try parsing from the end
              let jsonStr = codeBlockMatch?.[1] || triageJsonMatch?.[0];
              
              if (!jsonStr) {
                // Last resort: find all { } pairs and try parsing from the last one
                const allMatches = [...rawOutput.matchAll(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g)];
                for (let i = allMatches.length - 1; i >= 0; i--) {
                  try {
                    const candidate = JSON.parse(allMatches[i][0]);
                    if (candidate.type_label && candidate.area_label) {
                      jsonStr = allMatches[i][0];
                      break;
                    }
                  } catch {}
                }
              }
              
              if (jsonStr) {
                try {
                  parsed = JSON.parse(jsonStr.trim());
                } catch (e2) {
                  core.setFailed(`Failed to parse output: ${rawOutput}`);
                  return;
                }
              } else {
                core.setFailed(`Failed to parse output: ${rawOutput}`);
                return;
              }
            }
            
            const typeLabel = parsed.type_label;
            const areaLabel = parsed.area_label;
            const duplicateOf = parsed.duplicate_of;
            const suggestedResponse = parsed.suggested_response;
            
            // Validate required fields
            if (!typeLabel || !areaLabel || suggestedResponse === undefined) {
              core.setFailed(`Invalid JSON structure: missing required fields. Parsed: ${JSON.stringify(parsed)}`);
              return;
            }
            
            const validTypeLabels = ['bug', 'enhancement', 'documentation', 'question', 'invalid'];
            const validAreaLabels = ['area/auth', 'area/models', 'area/config', 'area/compat'];
            
            if (!validTypeLabels.includes(typeLabel)) {
              core.warning(`Invalid type_label: ${typeLabel}`);
              return;
            }
            if (!validAreaLabels.includes(areaLabel)) {
              core.warning(`Invalid area_label: ${areaLabel}`);
              return;
            }
            
            let labelsToAdd = [];
            let labelsToRemove = ['needs-triage'];
            
            // Remove existing triage labels to prevent conflicts
            validTypeLabels.forEach(label => labelsToRemove.push(label));
            validAreaLabels.forEach(label => labelsToRemove.push(label));
            
            // Only add one type label and one area label
            if (typeLabel && validTypeLabels.includes(typeLabel)) {
              labelsToAdd.push(typeLabel);
            }
            if (areaLabel && validAreaLabels.includes(areaLabel)) {
              labelsToAdd.push(areaLabel);
            }
            if (duplicateOf) {
              labelsToAdd.push('duplicate');
            }
            
            // Validate single label selection and deduplicate
            const typeLabels = labelsToAdd.filter(label => validTypeLabels.includes(label));
            const areaLabels = labelsToAdd.filter(label => validAreaLabels.includes(label));
            const otherLabels = labelsToAdd.filter(label => !validTypeLabels.includes(label) && !validAreaLabels.includes(label));
            
            if (typeLabels.length > 1) {
              core.warning(`Multiple type labels detected: ${typeLabels.join(', ')}. Using only: ${typeLabel}`);
            }
            if (areaLabels.length > 1) {
              core.warning(`Multiple area labels detected: ${areaLabels.join(', ')}. Using only: ${areaLabel}`);
            }
            
            // Rebuild labelsToAdd with single type and area labels
            labelsToAdd = [];
            if (typeLabel && validTypeLabels.includes(typeLabel)) {
              labelsToAdd.push(typeLabel);
            }
            if (areaLabel && validAreaLabels.includes(areaLabel)) {
              labelsToAdd.push(areaLabel);
            }
            labelsToAdd.push(...otherLabels);
            
            // Avoid removing labels that are being added
            labelsToRemove = labelsToRemove.filter(label => !labelsToAdd.includes(label));
            
            const issueNumber = parseInt(process.env.ISSUE_NUMBER);
            
            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: issueNumber,
              labels: labelsToAdd
            });
            core.info(`Added labels: ${labelsToAdd.join(', ')}`);
            
            for (const label of labelsToRemove) {
              try {
                await github.rest.issues.removeLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: issueNumber,
                  name: label
                });
                core.info(`Removed label: ${label}`);
              } catch (e) {
                core.info(`Label ${label} not present, skipping removal`);
              }
            }
            
            if (suggestedResponse && suggestedResponse.trim()) {
              let body = `👋 Thanks for opening this issue!\n\n${suggestedResponse}`;
              
              if (duplicateOf) {
                body += `\n\n🔗 This appears to be related to #${duplicateOf}. Please check that issue for updates.`;
              }
              
              body += `\n\n---\n*This is an automated response. A maintainer will review your issue soon.*`;
              
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issueNumber,
                body: body
              });
              core.info('Posted response comment');
            }
            
            if (duplicateOf) {
              await github.rest.issues.update({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: issueNumber,
                state: 'closed',
                state_reason: 'not_planned'
              });
              core.info(`Closed as duplicate of #${duplicateOf}`);
            }

      - name: 'Comment on failure'
        if: failure()
        uses: 'actions/github-script@v7'
        env:
          ISSUE_NUMBER: '${{ steps.get_issue.outputs.number || github.event.issue.number }}'
          RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
        with:
          script: |
            if (!process.env.ISSUE_NUMBER) return;
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: parseInt(process.env.ISSUE_NUMBER),
              body: `⚠️ Automated triage failed. [View logs](${process.env.RUN_URL})`
            });
