// Copyright 2023 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"time"
)

// CopilotService provides access to the Copilot-related functions
// in the GitHub API.
//
// GitHub API docs: https://docs.github.com/rest/copilot?apiVersion=2022-11-28
type CopilotService service

// CopilotOrganizationDetails represents the details of an organization's Copilot for Business subscription.
type CopilotOrganizationDetails struct {
	SeatBreakdown         *CopilotSeatBreakdown `json:"seat_breakdown"`
	PublicCodeSuggestions string                `json:"public_code_suggestions"`
	CopilotChat           string                `json:"copilot_chat"`
	SeatManagementSetting string                `json:"seat_management_setting"`
}

// CopilotSeatBreakdown represents the breakdown of Copilot for Business seats for the organization.
type CopilotSeatBreakdown struct {
	Total               int `json:"total"`
	AddedThisCycle      int `json:"added_this_cycle"`
	PendingCancellation int `json:"pending_cancellation"`
	PendingInvitation   int `json:"pending_invitation"`
	ActiveThisCycle     int `json:"active_this_cycle"`
	InactiveThisCycle   int `json:"inactive_this_cycle"`
}

// ListCopilotSeatsResponse represents the Copilot for Business seat assignments for an organization.
type ListCopilotSeatsResponse struct {
	TotalSeats int64                 `json:"total_seats"`
	Seats      []*CopilotSeatDetails `json:"seats"`
}

// CopilotSeatDetails represents the details of a Copilot for Business seat.
type CopilotSeatDetails struct {
	// Assignee can either be a User, Team, or Organization.
	Assignee                any        `json:"assignee"`
	AssigningTeam           *Team      `json:"assigning_team,omitempty"`
	PendingCancellationDate *string    `json:"pending_cancellation_date,omitempty"`
	LastActivityAt          *Timestamp `json:"last_activity_at,omitempty"`
	LastActivityEditor      *string    `json:"last_activity_editor,omitempty"`
	CreatedAt               *Timestamp `json:"created_at"`
	UpdatedAt               *Timestamp `json:"updated_at,omitempty"`
	PlanType                *string    `json:"plan_type,omitempty"`
}

// SeatAssignments represents the number of seats assigned.
type SeatAssignments struct {
	SeatsCreated int `json:"seats_created"`
}

// SeatCancellations represents the number of seats cancelled.
type SeatCancellations struct {
	SeatsCancelled int `json:"seats_cancelled"`
}

// CopilotMetricsListOptions represents the optional parameters to the CopilotService get metrics methods.
type CopilotMetricsListOptions struct {
	Since *time.Time `url:"since,omitempty"`
	Until *time.Time `url:"until,omitempty"`

	ListOptions
}

// CopilotIDECodeCompletionsLanguage represents Copilot usage metrics for completions in the IDE for a language.
type CopilotIDECodeCompletionsLanguage struct {
	Name              string `json:"name"`
	TotalEngagedUsers int    `json:"total_engaged_users"`
}

// CopilotIDECodeCompletionsModelLanguage represents Copilot usage metrics for completions in the IDE for a model and language.
type CopilotIDECodeCompletionsModelLanguage struct {
	Name                    string `json:"name"`
	TotalEngagedUsers       int    `json:"total_engaged_users"`
	TotalCodeSuggestions    int    `json:"total_code_suggestions"`
	TotalCodeAcceptances    int    `json:"total_code_acceptances"`
	TotalCodeLinesSuggested int    `json:"total_code_lines_suggested"`
	TotalCodeLinesAccepted  int    `json:"total_code_lines_accepted"`
}

// CopilotIDECodeCompletionsModel represents Copilot usage metrics for completions in the IDE for a model.
type CopilotIDECodeCompletionsModel struct {
	Name                    string                                    `json:"name"`
	IsCustomModel           bool                                      `json:"is_custom_model"`
	CustomModelTrainingDate *string                                   `json:"custom_model_training_date,omitempty"`
	TotalEngagedUsers       int                                       `json:"total_engaged_users"`
	Languages               []*CopilotIDECodeCompletionsModelLanguage `json:"languages"`
}

// CopilotIDECodeCompletionsEditor represents Copilot usage metrics for completions in the IDE for an editor.
type CopilotIDECodeCompletionsEditor struct {
	Name              string                            `json:"name"`
	TotalEngagedUsers int                               `json:"total_engaged_users"`
	Models            []*CopilotIDECodeCompletionsModel `json:"models"`
}

// CopilotIDECodeCompletions represents Copilot usage metrics for Copilot code completions in the IDE, categorized by editor, model and language.
type CopilotIDECodeCompletions struct {
	TotalEngagedUsers int                                  `json:"total_engaged_users"`
	Languages         []*CopilotIDECodeCompletionsLanguage `json:"languages"`
	Editors           []*CopilotIDECodeCompletionsEditor   `json:"editors"`
}

// CopilotIDEChatModel represents Copilot usage metrics for chatting with a model in the IDE.
type CopilotIDEChatModel struct {
	Name                     string  `json:"name"`
	IsCustomModel            bool    `json:"is_custom_model"`
	CustomModelTrainingDate  *string `json:"custom_model_training_date,omitempty"`
	TotalEngagedUsers        int     `json:"total_engaged_users"`
	TotalChats               int     `json:"total_chats"`
	TotalChatInsertionEvents int     `json:"total_chat_insertion_events"`
	TotalChatCopyEvents      int     `json:"total_chat_copy_events"`
}

// CopilotIDEChatEditor represents Copilot usage metrics for chatting with a model in the IDE, categorized by editor and model.
type CopilotIDEChatEditor struct {
	Name              string                 `json:"name"`
	TotalEngagedUsers int                    `json:"total_engaged_users"`
	Models            []*CopilotIDEChatModel `json:"models"`
}

// CopilotIDEChat represents Copilot usage metrics for Copilot Chat in the IDE, categorized by editor and model.
type CopilotIDEChat struct {
	TotalEngagedUsers int                     `json:"total_engaged_users"`
	Editors           []*CopilotIDEChatEditor `json:"editors"`
}

// CopilotDotcomChatModel represents Copilot usage metrics for chatting with a model in the webbrowser.
type CopilotDotcomChatModel struct {
	Name                    string  `json:"name"`
	IsCustomModel           bool    `json:"is_custom_model"`
	CustomModelTrainingDate *string `json:"custom_model_training_date,omitempty"`
	TotalEngagedUsers       int     `json:"total_engaged_users"`
	TotalChats              int     `json:"total_chats"`
}

// CopilotDotcomChat represents Copilot usage metrics for Copilot Chat in the webbrowser, categorized by model.
type CopilotDotcomChat struct {
	TotalEngagedUsers int                       `json:"total_engaged_users"`
	Models            []*CopilotDotcomChatModel `json:"models"`
}

// CopilotDotcomPullRequestsModel represents Copilot usage metrics for pull requests in the webbrowser, categorized by model.
type CopilotDotcomPullRequestsModel struct {
	Name                    string  `json:"name"`
	IsCustomModel           bool    `json:"is_custom_model"`
	CustomModelTrainingDate *string `json:"custom_model_training_date,omitempty"`
	TotalPRSummariesCreated int     `json:"total_pr_summaries_created"`
	TotalEngagedUsers       int     `json:"total_engaged_users"`
}

// CopilotDotcomPullRequestsRepository represents Copilot usage metrics for pull requests in the webbrowser, categorized by repository.
type CopilotDotcomPullRequestsRepository struct {
	Name              string                            `json:"name"`
	TotalEngagedUsers int                               `json:"total_engaged_users"`
	Models            []*CopilotDotcomPullRequestsModel `json:"models"`
}

// CopilotDotcomPullRequests represents Copilot usage metrics for pull requests in the webbrowser, categorized by repository and model.
type CopilotDotcomPullRequests struct {
	TotalEngagedUsers int                                    `json:"total_engaged_users"`
	Repositories      []*CopilotDotcomPullRequestsRepository `json:"repositories"`
}

// CopilotMetrics represents Copilot usage metrics for a given day.
type CopilotMetrics struct {
	Date                      string                     `json:"date"`
	TotalActiveUsers          *int                       `json:"total_active_users,omitempty"`
	TotalEngagedUsers         *int                       `json:"total_engaged_users,omitempty"`
	CopilotIDECodeCompletions *CopilotIDECodeCompletions `json:"copilot_ide_code_completions,omitempty"`
	CopilotIDEChat            *CopilotIDEChat            `json:"copilot_ide_chat,omitempty"`
	CopilotDotcomChat         *CopilotDotcomChat         `json:"copilot_dotcom_chat,omitempty"`
	CopilotDotcomPullRequests *CopilotDotcomPullRequests `json:"copilot_dotcom_pull_requests,omitempty"`
}

// CopilotMetricsReportOptions specifies the optional parameters for single-day metrics report endpoints.
type CopilotMetricsReportOptions struct {
	Day string `url:"day"` // Required, format: YYYY-MM-DD
}

// CopilotDailyMetricsReport represents the response from 1-day Copilot metrics report endpoints.
type CopilotDailyMetricsReport struct {
	DownloadLinks []string `json:"download_links"`
	ReportDay     string   `json:"report_day"`
}

// CopilotMetricsReport represents the response from 28-day Copilot metrics report endpoints.
type CopilotMetricsReport struct {
	DownloadLinks  []string `json:"download_links"`
	ReportStartDay string   `json:"report_start_day"`
	ReportEndDay   string   `json:"report_end_day"`
}

// UnmarshalJSON implements the json.Unmarshaler interface.
func (cp *CopilotSeatDetails) UnmarshalJSON(data []byte) error {
	// Using an alias to avoid infinite recursion when calling json.Unmarshal
	type alias CopilotSeatDetails
	var seatDetail alias

	if err := json.Unmarshal(data, &seatDetail); err != nil {
		return err
	}

	cp.AssigningTeam = seatDetail.AssigningTeam
	cp.PendingCancellationDate = seatDetail.PendingCancellationDate
	cp.LastActivityAt = seatDetail.LastActivityAt
	cp.LastActivityEditor = seatDetail.LastActivityEditor
	cp.CreatedAt = seatDetail.CreatedAt
	cp.UpdatedAt = seatDetail.UpdatedAt
	cp.PlanType = seatDetail.PlanType

	switch v := seatDetail.Assignee.(type) {
	case nil:
		// Assignee can be null according to GitHub API specification.
		// See: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#list-all-copilot-seat-assignments-for-an-organization
		// Note: Copilot API is in public preview and subject to change.
		cp.Assignee = nil
	case map[string]any:
		jsonData, err := json.Marshal(seatDetail.Assignee)
		if err != nil {
			return err
		}

		if v["type"] == nil {
			return errors.New("assignee type field is not set")
		}

		if t, ok := v["type"].(string); ok && t == "User" {
			var user *User
			if err := json.Unmarshal(jsonData, &user); err != nil {
				return err
			}
			cp.Assignee = user
		} else if t, ok := v["type"].(string); ok && t == "Team" {
			var team *Team
			if err := json.Unmarshal(jsonData, &team); err != nil {
				return err
			}
			cp.Assignee = team
		} else if t, ok := v["type"].(string); ok && t == "Organization" {
			var organization *Organization
			if err := json.Unmarshal(jsonData, &organization); err != nil {
				return err
			}
			cp.Assignee = organization
		} else {
			return fmt.Errorf("unsupported assignee type %v", v["type"])
		}
	default:
		return fmt.Errorf("unsupported assignee type %T", v)
	}

	return nil
}

// GetUser gets the User from the CopilotSeatDetails if the assignee is a user.
func (cp *CopilotSeatDetails) GetUser() (*User, bool) { u, ok := cp.Assignee.(*User); return u, ok }

// GetTeam gets the Team from the CopilotSeatDetails if the assignee is a team.
func (cp *CopilotSeatDetails) GetTeam() (*Team, bool) { t, ok := cp.Assignee.(*Team); return t, ok }

// GetOrganization gets the Organization from the CopilotSeatDetails if the assignee is an organization.
func (cp *CopilotSeatDetails) GetOrganization() (*Organization, bool) {
	o, ok := cp.Assignee.(*Organization)
	return o, ok
}

// GetCopilotBilling gets Copilot for Business billing information and settings for an organization.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#get-copilot-seat-information-and-settings-for-an-organization
//
//meta:operation GET /orgs/{org}/copilot/billing
func (s *CopilotService) GetCopilotBilling(ctx context.Context, org string) (*CopilotOrganizationDetails, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/billing", org)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var copilotDetails *CopilotOrganizationDetails
	resp, err := s.client.Do(req, &copilotDetails)
	if err != nil {
		return nil, resp, err
	}

	return copilotDetails, resp, nil
}

// ListCopilotSeats lists Copilot for Business seat assignments for an organization.
//
// To paginate through all seats, populate 'Page' with the number of the last page.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#list-all-copilot-seat-assignments-for-an-organization
//
//meta:operation GET /orgs/{org}/copilot/billing/seats
func (s *CopilotService) ListCopilotSeats(ctx context.Context, org string, opts *ListOptions) (*ListCopilotSeatsResponse, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/billing/seats", org)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var copilotSeats *ListCopilotSeatsResponse
	resp, err := s.client.Do(req, &copilotSeats)
	if err != nil {
		return nil, resp, err
	}

	return copilotSeats, resp, nil
}

// ListCopilotEnterpriseSeats lists Copilot for Business seat assignments for an enterprise.
//
// To paginate through all seats, populate 'Page' with the number of the last page.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/copilot/copilot-user-management?apiVersion=2022-11-28#list-all-copilot-seat-assignments-for-an-enterprise
//
//meta:operation GET /enterprises/{enterprise}/copilot/billing/seats
func (s *CopilotService) ListCopilotEnterpriseSeats(ctx context.Context, enterprise string, opts *ListOptions) (*ListCopilotSeatsResponse, *Response, error) {
	u := fmt.Sprintf("enterprises/%v/copilot/billing/seats", enterprise)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var copilotSeats *ListCopilotSeatsResponse
	resp, err := s.client.Do(req, &copilotSeats)
	if err != nil {
		return nil, resp, err
	}

	return copilotSeats, resp, nil
}

// ListOrganizationCopilotCodingAgentRepositoriesResponse represents the response from listing
// repositories enabled for the Copilot coding agent in an organization.
type ListOrganizationCopilotCodingAgentRepositoriesResponse struct {
	TotalCount   int           `json:"total_count"`
	Repositories []*Repository `json:"repositories"`
}

// ListOrganizationCodingAgentRepositories lists repositories enabled for the Copilot coding agent in an organization.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-coding-agent-management?apiVersion=2022-11-28#list-repositories-enabled-for-copilot-cloud-agent-in-an-organization
//
//meta:operation GET /orgs/{org}/copilot/coding-agent/permissions/repositories
func (s *CopilotService) ListOrganizationCodingAgentRepositories(ctx context.Context, org string, opts *ListOptions) (*ListOrganizationCopilotCodingAgentRepositoriesResponse, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/coding-agent/permissions/repositories", org)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var result *ListOrganizationCopilotCodingAgentRepositoriesResponse
	resp, err := s.client.Do(req, &result)
	if err != nil {
		return nil, resp, err
	}

	return result, resp, nil
}

// CopilotOrganizationContentExclusionDetails lists all Copilot content exclusion
// rules for an organization, keyed by repository full name. Each value is the
// list of file paths excluded from Copilot for that repository.
type CopilotOrganizationContentExclusionDetails map[string][]string

// GetOrganizationContentExclusionDetails gets the Copilot content exclusion rules for an organization.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-content-exclusion-management?apiVersion=2022-11-28#get-copilot-content-exclusion-rules-for-an-organization
//
//meta:operation GET /orgs/{org}/copilot/content_exclusion
func (s *CopilotService) GetOrganizationContentExclusionDetails(ctx context.Context, org string) (CopilotOrganizationContentExclusionDetails, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/content_exclusion", org)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	details := CopilotOrganizationContentExclusionDetails{}
	resp, err := s.client.Do(req, &details)
	if err != nil {
		return nil, resp, err
	}

	return details, resp, nil
}

// AddCopilotTeams adds teams to the Copilot for Business subscription for an organization.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#add-teams-to-the-copilot-subscription-for-an-organization
//
//meta:operation POST /orgs/{org}/copilot/billing/selected_teams
func (s *CopilotService) AddCopilotTeams(ctx context.Context, org string, teamNames []string) (*SeatAssignments, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/billing/selected_teams", org)

	body := struct {
		SelectedTeams []string `json:"selected_teams"`
	}{
		SelectedTeams: teamNames,
	}

	req, err := s.client.NewRequest(ctx, "POST", u, body)
	if err != nil {
		return nil, nil, err
	}

	var seatAssignments *SeatAssignments
	resp, err := s.client.Do(req, &seatAssignments)
	if err != nil {
		return nil, resp, err
	}

	return seatAssignments, resp, nil
}

// RemoveCopilotTeams removes teams from the Copilot for Business subscription for an organization.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#remove-teams-from-the-copilot-subscription-for-an-organization
//
//meta:operation DELETE /orgs/{org}/copilot/billing/selected_teams
func (s *CopilotService) RemoveCopilotTeams(ctx context.Context, org string, teamNames []string) (*SeatCancellations, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/billing/selected_teams", org)

	body := struct {
		SelectedTeams []string `json:"selected_teams"`
	}{
		SelectedTeams: teamNames,
	}

	req, err := s.client.NewRequest(ctx, "DELETE", u, body)
	if err != nil {
		return nil, nil, err
	}

	var seatCancellations *SeatCancellations
	resp, err := s.client.Do(req, &seatCancellations)
	if err != nil {
		return nil, resp, err
	}

	return seatCancellations, resp, nil
}

// AddCopilotUsers adds users to the Copilot for Business subscription for an organization
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#add-users-to-the-copilot-subscription-for-an-organization
//
//meta:operation POST /orgs/{org}/copilot/billing/selected_users
func (s *CopilotService) AddCopilotUsers(ctx context.Context, org string, users []string) (*SeatAssignments, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/billing/selected_users", org)

	body := struct {
		SelectedUsernames []string `json:"selected_usernames"`
	}{
		SelectedUsernames: users,
	}

	req, err := s.client.NewRequest(ctx, "POST", u, body)
	if err != nil {
		return nil, nil, err
	}

	var seatAssignments *SeatAssignments
	resp, err := s.client.Do(req, &seatAssignments)
	if err != nil {
		return nil, resp, err
	}

	return seatAssignments, resp, nil
}

// RemoveCopilotUsers removes users from the Copilot for Business subscription for an organization.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#remove-users-from-the-copilot-subscription-for-an-organization
//
//meta:operation DELETE /orgs/{org}/copilot/billing/selected_users
func (s *CopilotService) RemoveCopilotUsers(ctx context.Context, org string, users []string) (*SeatCancellations, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/billing/selected_users", org)

	body := struct {
		SelectedUsernames []string `json:"selected_usernames"`
	}{
		SelectedUsernames: users,
	}

	req, err := s.client.NewRequest(ctx, "DELETE", u, body)
	if err != nil {
		return nil, nil, err
	}

	var seatCancellations *SeatCancellations
	resp, err := s.client.Do(req, &seatCancellations)
	if err != nil {
		return nil, resp, err
	}

	return seatCancellations, resp, nil
}

// GetSeatDetails gets Copilot for Business seat assignment details for a user.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management?apiVersion=2022-11-28#get-copilot-seat-assignment-details-for-a-user
//
//meta:operation GET /orgs/{org}/members/{username}/copilot
func (s *CopilotService) GetSeatDetails(ctx context.Context, org, user string) (*CopilotSeatDetails, *Response, error) {
	u := fmt.Sprintf("orgs/%v/members/%v/copilot", org, user)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var seatDetails *CopilotSeatDetails
	resp, err := s.client.Do(req, &seatDetails)
	if err != nil {
		return nil, resp, err
	}

	return seatDetails, resp, nil
}

// GetEnterpriseMetrics gets Copilot usage metrics for an enterprise.
//
// Deprecated: This endpoint was closed down on April 2, 2026 for github.com.
// It may still be available for GitHub Enterprise Server. Use GetEnterpriseDailyMetricsReport
// or GetEnterpriseMetricsReport instead.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/copilot/copilot-metrics?apiVersion=2022-11-28#get-copilot-metrics-for-an-enterprise
//
//meta:operation GET /enterprises/{enterprise}/copilot/metrics
func (s *CopilotService) GetEnterpriseMetrics(ctx context.Context, enterprise string, opts *CopilotMetricsListOptions) ([]*CopilotMetrics, *Response, error) {
	u := fmt.Sprintf("enterprises/%v/copilot/metrics", enterprise)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var metrics []*CopilotMetrics
	resp, err := s.client.Do(req, &metrics)
	if err != nil {
		return nil, resp, err
	}

	return metrics, resp, nil
}

// GetEnterpriseTeamMetrics gets Copilot usage metrics for an enterprise team.
//
// Deprecated: This endpoint was closed down on April 2, 2026 for github.com.
// It may still be available for GitHub Enterprise Server. Use GetEnterpriseDailyMetricsReport
// or GetEnterpriseMetricsReport instead.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/copilot/copilot-metrics?apiVersion=2022-11-28#get-copilot-metrics-for-an-enterprise-team
//
//meta:operation GET /enterprises/{enterprise}/team/{team_slug}/copilot/metrics
func (s *CopilotService) GetEnterpriseTeamMetrics(ctx context.Context, enterprise, team string, opts *CopilotMetricsListOptions) ([]*CopilotMetrics, *Response, error) {
	u := fmt.Sprintf("enterprises/%v/team/%v/copilot/metrics", enterprise, team)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var metrics []*CopilotMetrics
	resp, err := s.client.Do(req, &metrics)
	if err != nil {
		return nil, resp, err
	}

	return metrics, resp, nil
}

// GetOrganizationMetrics gets Copilot usage metrics for an organization.
//
// Deprecated: This endpoint was closed down on April 2, 2026 for github.com.
// It may still be available for GitHub Enterprise Server. Use GetOrganizationDailyMetricsReport
// or GetOrganizationMetricsReport instead.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-metrics?apiVersion=2022-11-28#get-copilot-metrics-for-an-organization
//
//meta:operation GET /orgs/{org}/copilot/metrics
func (s *CopilotService) GetOrganizationMetrics(ctx context.Context, org string, opts *CopilotMetricsListOptions) ([]*CopilotMetrics, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/metrics", org)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var metrics []*CopilotMetrics
	resp, err := s.client.Do(req, &metrics)
	if err != nil {
		return nil, resp, err
	}

	return metrics, resp, nil
}

// GetOrganizationTeamMetrics gets Copilot usage metrics for an organization team.
//
// Deprecated: This endpoint was closed down on April 2, 2026 for github.com.
// It may still be available for GitHub Enterprise Server. Use GetOrganizationDailyMetricsReport
// or GetOrganizationMetricsReport instead.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-metrics?apiVersion=2022-11-28#get-copilot-metrics-for-a-team
//
//meta:operation GET /orgs/{org}/team/{team_slug}/copilot/metrics
func (s *CopilotService) GetOrganizationTeamMetrics(ctx context.Context, org, team string, opts *CopilotMetricsListOptions) ([]*CopilotMetrics, *Response, error) {
	u := fmt.Sprintf("orgs/%v/team/%v/copilot/metrics", org, team)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var metrics []*CopilotMetrics
	resp, err := s.client.Do(req, &metrics)
	if err != nil {
		return nil, resp, err
	}

	return metrics, resp, nil
}

// GetEnterpriseDailyMetricsReport gets a report containing Copilot metrics for a single day for an enterprise.
//
// Use DownloadDailyMetrics to decode the payloads served at the returned download links.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage-metrics?apiVersion=2022-11-28#get-copilot-enterprise-usage-metrics-for-a-specific-day
//
//meta:operation GET /enterprises/{enterprise}/copilot/metrics/reports/enterprise-1-day
func (s *CopilotService) GetEnterpriseDailyMetricsReport(ctx context.Context, enterprise string, opts *CopilotMetricsReportOptions) (*CopilotDailyMetricsReport, *Response, error) {
	u := fmt.Sprintf("enterprises/%v/copilot/metrics/reports/enterprise-1-day", enterprise)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var report *CopilotDailyMetricsReport
	resp, err := s.client.Do(req, &report)
	if err != nil {
		return nil, resp, err
	}

	return report, resp, nil
}

// GetEnterpriseMetricsReport gets a report containing Copilot metrics for a 28-day rolling window for an enterprise.
//
// Use DownloadPeriodicMetrics to decode the payloads served at the returned download links.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage-metrics?apiVersion=2022-11-28#get-copilot-enterprise-usage-metrics
//
//meta:operation GET /enterprises/{enterprise}/copilot/metrics/reports/enterprise-28-day/latest
func (s *CopilotService) GetEnterpriseMetricsReport(ctx context.Context, enterprise string) (*CopilotMetricsReport, *Response, error) {
	u := fmt.Sprintf("enterprises/%v/copilot/metrics/reports/enterprise-28-day/latest", enterprise)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var report *CopilotMetricsReport
	resp, err := s.client.Do(req, &report)
	if err != nil {
		return nil, resp, err
	}

	return report, resp, nil
}

// GetEnterpriseUsersDailyMetricsReport gets a report containing Copilot user metrics for a single day for an enterprise.
//
// Use DownloadUserDailyMetrics to decode the payloads served at the returned download links.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage-metrics?apiVersion=2022-11-28#get-copilot-users-usage-metrics-for-a-specific-day
//
//meta:operation GET /enterprises/{enterprise}/copilot/metrics/reports/users-1-day
func (s *CopilotService) GetEnterpriseUsersDailyMetricsReport(ctx context.Context, enterprise string, opts *CopilotMetricsReportOptions) (*CopilotDailyMetricsReport, *Response, error) {
	u := fmt.Sprintf("enterprises/%v/copilot/metrics/reports/users-1-day", enterprise)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var report *CopilotDailyMetricsReport
	resp, err := s.client.Do(req, &report)
	if err != nil {
		return nil, resp, err
	}

	return report, resp, nil
}

// GetEnterpriseUsersMetricsReport gets a report containing Copilot user metrics for a 28-day rolling window for an enterprise.
//
// Use DownloadUserPeriodicMetrics to decode the payloads served at the returned download links.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage-metrics?apiVersion=2022-11-28#get-copilot-users-usage-metrics
//
//meta:operation GET /enterprises/{enterprise}/copilot/metrics/reports/users-28-day/latest
func (s *CopilotService) GetEnterpriseUsersMetricsReport(ctx context.Context, enterprise string) (*CopilotMetricsReport, *Response, error) {
	u := fmt.Sprintf("enterprises/%v/copilot/metrics/reports/users-28-day/latest", enterprise)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var report *CopilotMetricsReport
	resp, err := s.client.Do(req, &report)
	if err != nil {
		return nil, resp, err
	}

	return report, resp, nil
}

// GetOrganizationDailyMetricsReport gets a report containing Copilot metrics for a single day for an organization.
//
// Use DownloadDailyMetrics to decode the payloads served at the returned download links.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage-metrics?apiVersion=2022-11-28#get-copilot-organization-usage-metrics-for-a-specific-day
//
//meta:operation GET /orgs/{org}/copilot/metrics/reports/organization-1-day
func (s *CopilotService) GetOrganizationDailyMetricsReport(ctx context.Context, org string, opts *CopilotMetricsReportOptions) (*CopilotDailyMetricsReport, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/metrics/reports/organization-1-day", org)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var report *CopilotDailyMetricsReport
	resp, err := s.client.Do(req, &report)
	if err != nil {
		return nil, resp, err
	}

	return report, resp, nil
}

// GetOrganizationMetricsReport gets a report containing Copilot metrics for a 28-day rolling window for an organization.
//
// Use DownloadPeriodicMetrics to decode the payloads served at the returned download links.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage-metrics?apiVersion=2022-11-28#get-copilot-organization-usage-metrics
//
//meta:operation GET /orgs/{org}/copilot/metrics/reports/organization-28-day/latest
func (s *CopilotService) GetOrganizationMetricsReport(ctx context.Context, org string) (*CopilotMetricsReport, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/metrics/reports/organization-28-day/latest", org)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var report *CopilotMetricsReport
	resp, err := s.client.Do(req, &report)
	if err != nil {
		return nil, resp, err
	}

	return report, resp, nil
}

// GetOrganizationUsersDailyMetricsReport gets a report containing Copilot user metrics for a single day for an organization.
//
// Use DownloadUserDailyMetrics to decode the payloads served at the returned download links.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage-metrics?apiVersion=2022-11-28#get-copilot-organization-users-usage-metrics-for-a-specific-day
//
//meta:operation GET /orgs/{org}/copilot/metrics/reports/users-1-day
func (s *CopilotService) GetOrganizationUsersDailyMetricsReport(ctx context.Context, org string, opts *CopilotMetricsReportOptions) (*CopilotDailyMetricsReport, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/metrics/reports/users-1-day", org)
	u, err := addOptions(u, opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var report *CopilotDailyMetricsReport
	resp, err := s.client.Do(req, &report)
	if err != nil {
		return nil, resp, err
	}

	return report, resp, nil
}

// GetOrganizationUsersMetricsReport gets a report containing Copilot user metrics for a 28-day rolling window for an organization.
//
// Use DownloadUserPeriodicMetrics to decode the payloads served at the returned download links.
//
// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage-metrics?apiVersion=2022-11-28#get-copilot-organization-users-usage-metrics
//
//meta:operation GET /orgs/{org}/copilot/metrics/reports/users-28-day/latest
func (s *CopilotService) GetOrganizationUsersMetricsReport(ctx context.Context, org string) (*CopilotMetricsReport, *Response, error) {
	u := fmt.Sprintf("orgs/%v/copilot/metrics/reports/users-28-day/latest", org)

	req, err := s.client.NewRequest(ctx, "GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	var report *CopilotMetricsReport
	resp, err := s.client.Do(req, &report)
	if err != nil {
		return nil, resp, err
	}

	return report, resp, nil
}

// DownloadCopilotMetrics downloads a Copilot metrics report from the provided download link
// and decodes it as a []*CopilotMetrics.
//
// Deprecated: Use DownloadDailyMetrics,
// DownloadPeriodicMetrics, DownloadUserDailyMetrics, DownloadUserPeriodicMetrics instead.
// The payloads served at the download links returned by the new
// Get*MetricsReport endpoints on GitHub.com do not match the CopilotMetrics shape
// (see https://github.com/google/go-github/issues/4136).
// This method is retained
// for GitHub Enterprise Server installations that may still serve the legacy shape.
func (s *CopilotService) DownloadCopilotMetrics(ctx context.Context, url string) ([]*CopilotMetrics, *Response, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	resp, err := s.client.BareDo(req)
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	var metrics []*CopilotMetrics
	if err := json.NewDecoder(resp.Body).Decode(&metrics); err != nil {
		return nil, resp, err
	}

	return metrics, resp, nil
}

// CopilotMetricsPullRequests represents pull request totals in a Copilot metrics report.
type CopilotMetricsPullRequests struct {
	TotalReviewed                       *int     `json:"total_reviewed,omitempty"`
	TotalCreated                        *int     `json:"total_created,omitempty"`
	TotalCreatedByCopilot               *int     `json:"total_created_by_copilot,omitempty"`
	TotalReviewedByCopilot              *int     `json:"total_reviewed_by_copilot,omitempty"`
	TotalMerged                         *int     `json:"total_merged,omitempty"`
	MedianMinutesToMerge                *float64 `json:"median_minutes_to_merge,omitempty"`
	TotalSuggestions                    *int     `json:"total_suggestions,omitempty"`
	TotalAppliedSuggestions             *int     `json:"total_applied_suggestions,omitempty"`
	TotalMergedCreatedByCopilot         *int     `json:"total_merged_created_by_copilot,omitempty"`
	MedianMinutesToMergeCopilotAuthored *float64 `json:"median_minutes_to_merge_copilot_authored,omitempty"`
	TotalCopilotSuggestions             *int     `json:"total_copilot_suggestions,omitempty"`
	TotalCopilotAppliedSuggestions      *int     `json:"total_copilot_applied_suggestions,omitempty"`
	MedianMinutesToMergeCopilotReviewed *float64 `json:"median_minutes_to_merge_copilot_reviewed,omitempty"`
	TotalMergedReviewedByCopilot        *int     `json:"total_merged_reviewed_by_copilot,omitempty"`
}

// CopilotMetricsCodeActivity captures the code-generation activity counts and lines-of-code (LOC)
// suggestion totals shared across the per-IDE, per-feature, per-language, and per-model breakdowns
// in a Copilot metrics report.
type CopilotMetricsCodeActivity struct {
	CodeGenerationActivityCount *int `json:"code_generation_activity_count,omitempty"`
	CodeAcceptanceActivityCount *int `json:"code_acceptance_activity_count,omitempty"`
	LOCSuggestedToAddSum        *int `json:"loc_suggested_to_add_sum,omitempty"`
	LOCSuggestedToDeleteSum     *int `json:"loc_suggested_to_delete_sum,omitempty"`
	LOCAddedSum                 *int `json:"loc_added_sum,omitempty"`
	LOCDeletedSum               *int `json:"loc_deleted_sum,omitempty"`
}

// CopilotMetricsChatPanel captures per-mode chat panel interaction counts shared across daily,
// periodic, and user metrics reports. Each field is a subset of user_initiated_interaction_count
// attributed to that chat panel mode.
type CopilotMetricsChatPanel struct {
	ChatPanelAgentMode   *int `json:"chat_panel_agent_mode,omitempty"`
	ChatPanelAskMode     *int `json:"chat_panel_ask_mode,omitempty"`
	ChatPanelCustomMode  *int `json:"chat_panel_custom_mode,omitempty"`
	ChatPanelEditMode    *int `json:"chat_panel_edit_mode,omitempty"`
	ChatPanelUnknownMode *int `json:"chat_panel_unknown_mode,omitempty"`
}

// CopilotMetricsIDE represents per-IDE aggregate totals in a Copilot metrics report.
type CopilotMetricsIDE struct {
	IDE                           string `json:"ide"`
	UserInitiatedInteractionCount *int   `json:"user_initiated_interaction_count,omitempty"`
	CopilotMetricsCodeActivity
}

// CopilotMetricsFeature represents per-feature aggregate totals in a Copilot metrics report.
type CopilotMetricsFeature struct {
	Feature                       string `json:"feature"`
	UserInitiatedInteractionCount *int   `json:"user_initiated_interaction_count,omitempty"`
	CopilotMetricsCodeActivity
}

// CopilotMetricsLanguageFeature represents per-language-feature totals in a Copilot metrics report.
type CopilotMetricsLanguageFeature struct {
	Language string `json:"language"`
	Feature  string `json:"feature"`
	CopilotMetricsCodeActivity
}

// CopilotMetricsLanguageModel represents per-language-model totals in a Copilot metrics report.
type CopilotMetricsLanguageModel struct {
	Language string `json:"language"`
	Model    string `json:"model"`
	CopilotMetricsCodeActivity
}

// CopilotMetricsModelFeature represents per-model-feature totals in a Copilot metrics report.
type CopilotMetricsModelFeature struct {
	Model                         string `json:"model"`
	Feature                       string `json:"feature"`
	UserInitiatedInteractionCount *int   `json:"user_initiated_interaction_count,omitempty"`
	CopilotMetricsCodeActivity
}

// CopilotMetricsCLIVersion represents the last known Copilot CLI version seen in a metrics report.
type CopilotMetricsCLIVersion struct {
	SampledAt  *Timestamp `json:"sampled_at,omitempty"`
	CLIVersion string     `json:"cli_version"`
}

// CopilotMetricsCLITokenUsage represents Copilot CLI token totals in a metrics report.
type CopilotMetricsCLITokenUsage struct {
	AvgTokensPerRequest *float64 `json:"avg_tokens_per_request,omitempty"`
	OutputTokensSum     *int     `json:"output_tokens_sum,omitempty"`
	PromptTokensSum     *int     `json:"prompt_tokens_sum,omitempty"`
}

// CopilotMetricsCLI represents Copilot CLI totals in a metrics report.
type CopilotMetricsCLI struct {
	SessionCount        *int                         `json:"session_count,omitempty"`
	RequestCount        *int                         `json:"request_count,omitempty"`
	PromptCount         *int                         `json:"prompt_count,omitempty"`
	TokenUsage          *CopilotMetricsCLITokenUsage `json:"token_usage,omitempty"`
	LastKnownCLIVersion *CopilotMetricsCLIVersion    `json:"last_known_cli_version,omitempty"`
}

// CopilotDailyMetrics represents the payload downloaded from a 1-day Copilot usage metrics report.
type CopilotDailyMetrics struct {
	Day                                 string  `json:"day"`
	OrganizationID                      *string `json:"organization_id,omitempty"`
	EnterpriseID                        *string `json:"enterprise_id,omitempty"`
	DailyActiveCLIUsers                 *int    `json:"daily_active_cli_users,omitempty"`
	DailyActiveUsers                    *int    `json:"daily_active_users,omitempty"`
	DailyActiveCopilotCloudAgentUsers   *int    `json:"daily_active_copilot_cloud_agent_users,omitempty"`
	WeeklyActiveUsers                   *int    `json:"weekly_active_users,omitempty"`
	WeeklyActiveCopilotCloudAgentUsers  *int    `json:"weekly_active_copilot_cloud_agent_users,omitempty"`
	MonthlyActiveUsers                  *int    `json:"monthly_active_users,omitempty"`
	MonthlyActiveChatUsers              *int    `json:"monthly_active_chat_users,omitempty"`
	MonthlyActiveAgentUsers             *int    `json:"monthly_active_agent_users,omitempty"`
	MonthlyActiveCopilotCloudAgentUsers *int    `json:"monthly_active_copilot_cloud_agent_users,omitempty"`
	UserInitiatedInteractionCount       *int    `json:"user_initiated_interaction_count,omitempty"`
	CopilotMetricsChatPanel
	CodeGenerationActivityCount *int                             `json:"code_generation_activity_count,omitempty"`
	CodeAcceptanceActivityCount *int                             `json:"code_acceptance_activity_count,omitempty"`
	TotalsByIDE                 []*CopilotMetricsIDE             `json:"totals_by_ide,omitempty"`
	TotalsByFeature             []*CopilotMetricsFeature         `json:"totals_by_feature,omitempty"`
	TotalsByLanguageFeature     []*CopilotMetricsLanguageFeature `json:"totals_by_language_feature,omitempty"`
	TotalsByLanguageModel       []*CopilotMetricsLanguageModel   `json:"totals_by_language_model,omitempty"`
	TotalsByModelFeature        []*CopilotMetricsModelFeature    `json:"totals_by_model_feature,omitempty"`
	TotalsByCLI                 *CopilotMetricsCLI               `json:"totals_by_cli,omitempty"`
	LOCSuggestedToAddSum        *int                             `json:"loc_suggested_to_add_sum,omitempty"`
	LOCSuggestedToDeleteSum     *int                             `json:"loc_suggested_to_delete_sum,omitempty"`
	LOCAddedSum                 *int                             `json:"loc_added_sum,omitempty"`
	LOCDeletedSum               *int                             `json:"loc_deleted_sum,omitempty"`
	PullRequests                *CopilotMetricsPullRequests      `json:"pull_requests,omitempty"`
}

// CopilotPeriodicMetrics represents the payload downloaded from a multi-day (e.g. 28-day rolling)
// Copilot usage metrics report. The DayTotals field contains one CopilotDailyMetrics entry per day
// in the reporting window. Window-level metadata (ReportStartDay, ReportEndDay, CreatedAt) lives on
// this parent struct; each DayTotals entry only populates fields scoped to that day.
type CopilotPeriodicMetrics struct {
	ReportStartDay string                 `json:"report_start_day"`
	ReportEndDay   string                 `json:"report_end_day"`
	OrganizationID *string                `json:"organization_id,omitempty"`
	EnterpriseID   *string                `json:"enterprise_id,omitempty"`
	CreatedAt      *Timestamp             `json:"created_at,omitempty"`
	DayTotals      []*CopilotDailyMetrics `json:"day_totals,omitempty"`
}

// CopilotUserMetricsPluginVersion represents the last known plugin version used in an IDE by a Copilot user.
type CopilotUserMetricsPluginVersion struct {
	SampledAt     *Timestamp `json:"sampled_at,omitempty"`
	Plugin        string     `json:"plugin"`
	PluginVersion string     `json:"plugin_version"`
}

// CopilotUserMetricsIDEVersion represents the last known IDE version used by a Copilot user.
type CopilotUserMetricsIDEVersion struct {
	SampledAt  *Timestamp `json:"sampled_at,omitempty"`
	IDEVersion string     `json:"ide_version"`
}

// CopilotUserMetricsIDE represents per-IDE totals for a single Copilot user in a user metrics report.
type CopilotUserMetricsIDE struct {
	IDE                           string `json:"ide"`
	UserInitiatedInteractionCount *int   `json:"user_initiated_interaction_count,omitempty"`
	CopilotMetricsCodeActivity
	LastKnownPluginVersion *CopilotUserMetricsPluginVersion `json:"last_known_plugin_version,omitempty"`
	LastKnownIDEVersion    *CopilotUserMetricsIDEVersion    `json:"last_known_ide_version,omitempty"`
}

// CopilotUserDailyMetrics represents a single user's per-day Copilot usage metrics record from a
// 1-day user metrics report. User metrics reports are served as newline-delimited JSON.
type CopilotUserDailyMetrics struct {
	UserID                        int     `json:"user_id"`
	UserLogin                     string  `json:"user_login"`
	Day                           string  `json:"day"`
	OrganizationID                *string `json:"organization_id,omitempty"`
	EnterpriseID                  *string `json:"enterprise_id,omitempty"`
	UserInitiatedInteractionCount *int    `json:"user_initiated_interaction_count,omitempty"`
	CopilotMetricsChatPanel
	CodeGenerationActivityCount  *int                             `json:"code_generation_activity_count,omitempty"`
	CodeAcceptanceActivityCount  *int                             `json:"code_acceptance_activity_count,omitempty"`
	TotalsByIDE                  []*CopilotUserMetricsIDE         `json:"totals_by_ide,omitempty"`
	TotalsByFeature              []*CopilotMetricsFeature         `json:"totals_by_feature,omitempty"`
	TotalsByLanguageFeature      []*CopilotMetricsLanguageFeature `json:"totals_by_language_feature,omitempty"`
	TotalsByLanguageModel        []*CopilotMetricsLanguageModel   `json:"totals_by_language_model,omitempty"`
	TotalsByModelFeature         []*CopilotMetricsModelFeature    `json:"totals_by_model_feature,omitempty"`
	TotalsByCLI                  *CopilotMetricsCLI               `json:"totals_by_cli,omitempty"`
	UsedAgent                    *bool                            `json:"used_agent,omitempty"`
	UsedChat                     *bool                            `json:"used_chat,omitempty"`
	UsedCLI                      *bool                            `json:"used_cli,omitempty"`
	UsedCopilotCodeReviewActive  *bool                            `json:"used_copilot_code_review_active,omitempty"`
	UsedCopilotCodeReviewPassive *bool                            `json:"used_copilot_code_review_passive,omitempty"`
	UsedCopilotCodingAgent       *bool                            `json:"used_copilot_coding_agent,omitempty"`
	LOCSuggestedToAddSum         *int                             `json:"loc_suggested_to_add_sum,omitempty"`
	LOCSuggestedToDeleteSum      *int                             `json:"loc_suggested_to_delete_sum,omitempty"`
	LOCAddedSum                  *int                             `json:"loc_added_sum,omitempty"`
	LOCDeletedSum                *int                             `json:"loc_deleted_sum,omitempty"`
}

// CopilotUserPeriodicMetrics represents a single user's per-day Copilot usage metrics record from a
// multi-day (e.g. 28-day rolling) user metrics report. User metrics reports are served as
// newline-delimited JSON.
type CopilotUserPeriodicMetrics struct {
	ReportStartDay                string  `json:"report_start_day"`
	ReportEndDay                  string  `json:"report_end_day"`
	Day                           string  `json:"day"`
	OrganizationID                *string `json:"organization_id,omitempty"`
	EnterpriseID                  *string `json:"enterprise_id,omitempty"`
	UserID                        int     `json:"user_id"`
	UserLogin                     string  `json:"user_login"`
	UserInitiatedInteractionCount *int    `json:"user_initiated_interaction_count,omitempty"`
	CopilotMetricsChatPanel
	CodeGenerationActivityCount  *int                             `json:"code_generation_activity_count,omitempty"`
	CodeAcceptanceActivityCount  *int                             `json:"code_acceptance_activity_count,omitempty"`
	TotalsByIDE                  []*CopilotUserMetricsIDE         `json:"totals_by_ide,omitempty"`
	TotalsByFeature              []*CopilotMetricsFeature         `json:"totals_by_feature,omitempty"`
	TotalsByLanguageFeature      []*CopilotMetricsLanguageFeature `json:"totals_by_language_feature,omitempty"`
	TotalsByLanguageModel        []*CopilotMetricsLanguageModel   `json:"totals_by_language_model,omitempty"`
	TotalsByModelFeature         []*CopilotMetricsModelFeature    `json:"totals_by_model_feature,omitempty"`
	TotalsByCLI                  *CopilotMetricsCLI               `json:"totals_by_cli,omitempty"`
	UsedAgent                    *bool                            `json:"used_agent,omitempty"`
	UsedChat                     *bool                            `json:"used_chat,omitempty"`
	UsedCLI                      *bool                            `json:"used_cli,omitempty"`
	UsedCopilotCodeReviewActive  *bool                            `json:"used_copilot_code_review_active,omitempty"`
	UsedCopilotCodeReviewPassive *bool                            `json:"used_copilot_code_review_passive,omitempty"`
	UsedCopilotCodingAgent       *bool                            `json:"used_copilot_coding_agent,omitempty"`
	LOCSuggestedToAddSum         *int                             `json:"loc_suggested_to_add_sum,omitempty"`
	LOCSuggestedToDeleteSum      *int                             `json:"loc_suggested_to_delete_sum,omitempty"`
	LOCAddedSum                  *int                             `json:"loc_added_sum,omitempty"`
	LOCDeletedSum                *int                             `json:"loc_deleted_sum,omitempty"`
}

// fetchMetricsReport performs a GET against the provided download URL and returns the raw
// http.Response. The caller is responsible for closing the body.
func (s *CopilotService) fetchMetricsReport(ctx context.Context, url string) (*http.Response, *Response, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, nil, err
	}

	resp, err := s.client.client.Do(req)
	if err != nil {
		return nil, nil, err
	}

	if err := CheckResponse(resp); err != nil {
		resp.Body.Close()
		return nil, newResponse(resp), err
	}

	return resp, newResponse(resp), nil
}

// decodeNDJSONMetrics streams a newline-delimited JSON response body into a slice of *T,
// returning a nil slice when the body is empty.
func decodeNDJSONMetrics[T any](r io.Reader) ([]*T, error) {
	var records []*T
	dec := json.NewDecoder(r)
	for {
		var rec *T
		if err := dec.Decode(&rec); err != nil {
			if errors.Is(err, io.EOF) {
				break
			}
			return nil, err
		}
		records = append(records, rec)
	}
	return records, nil
}

// DownloadDailyMetrics downloads the payload of a 1-day Copilot usage metrics report from a
// download link returned by GetEnterpriseDailyMetricsReport or GetOrganizationDailyMetricsReport.
func (s *CopilotService) DownloadDailyMetrics(ctx context.Context, url string) (*CopilotDailyMetrics, *Response, error) {
	resp, r, err := s.fetchMetricsReport(ctx, url)
	if err != nil {
		return nil, r, err
	}
	defer resp.Body.Close()

	var metrics *CopilotDailyMetrics
	if err := json.NewDecoder(resp.Body).Decode(&metrics); err != nil {
		return nil, r, err
	}

	return metrics, r, nil
}

// DownloadPeriodicMetrics downloads the payload of a multi-day (e.g. 28-day rolling) Copilot
// usage metrics report from a download link returned by GetEnterpriseMetricsReport or
// GetOrganizationMetricsReport.
func (s *CopilotService) DownloadPeriodicMetrics(ctx context.Context, url string) (*CopilotPeriodicMetrics, *Response, error) {
	resp, r, err := s.fetchMetricsReport(ctx, url)
	if err != nil {
		return nil, r, err
	}
	defer resp.Body.Close()

	var metrics *CopilotPeriodicMetrics
	if err := json.NewDecoder(resp.Body).Decode(&metrics); err != nil {
		return nil, r, err
	}

	return metrics, r, nil
}

// DownloadUserDailyMetrics downloads the payload of a 1-day Copilot user metrics report from a
// download link returned by GetEnterpriseUsersDailyMetricsReport or
// GetOrganizationUsersDailyMetricsReport.
//
// The response is newline-delimited JSON, with one CopilotUserDailyMetrics record per line.
func (s *CopilotService) DownloadUserDailyMetrics(ctx context.Context, url string) ([]*CopilotUserDailyMetrics, *Response, error) {
	resp, r, err := s.fetchMetricsReport(ctx, url)
	if err != nil {
		return nil, r, err
	}
	defer resp.Body.Close()

	records, err := decodeNDJSONMetrics[CopilotUserDailyMetrics](resp.Body)
	if err != nil {
		return nil, r, err
	}
	return records, r, nil
}

// DownloadUserPeriodicMetrics downloads the payload of a multi-day (e.g. 28-day rolling) Copilot
// user metrics report from a download link returned by GetEnterpriseUsersMetricsReport or
// GetOrganizationUsersMetricsReport.
//
// The response is newline-delimited JSON, with one CopilotUserPeriodicMetrics record per
// user-day in the reporting window.
func (s *CopilotService) DownloadUserPeriodicMetrics(ctx context.Context, url string) ([]*CopilotUserPeriodicMetrics, *Response, error) {
	resp, r, err := s.fetchMetricsReport(ctx, url)
	if err != nil {
		return nil, r, err
	}
	defer resp.Body.Close()

	records, err := decodeNDJSONMetrics[CopilotUserPeriodicMetrics](resp.Body)
	if err != nil {
		return nil, r, err
	}
	return records, r, nil
}
