package main

import (
	
	
	
	
	
	
	
	
	
	

	
	
	
	
	
	
)

// Config
const (
	csvFilename = "data/repo_stats.csv"
	owner       = "pancsta"         // e.g. "google"
	repo        = "asyncmachine-go" // e.g. "go-github"
)

var token = ""

// DailyStat represents one row in our CSV
type DailyStat struct {
	Date             string
	Views            int
	UniqueViews      int
	Clones           int
	UniqueClones     int
	ReleaseDownloads int // Snapshot of total downloads on this date
}

func init() {
	godotenv.Load()
	token = os.Getenv("GITHUB_TOKEN_STATS")
}

func main() {
	 := context.Background()
	getStats()
	genCharts()
	render("light", "white")
	render("dark", "#100c2a")
}

// ///// ///// /////

// ///// STATS

// ///// ///// /////

func getStats( context.Context) {

	// 1. Authenticate
	if token == "YOUR_TOKEN" {
		log.Fatal("Please update the token, owner, and repo constants in the code.")
	}
	 := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
	 := oauth2.NewClient(, )
	 := github.NewClient()

	// 2. Load existing CSV data into a map (Date -> Stat)
	 := loadExistingCSV(csvFilename)
	fmt.Printf("Loaded %d historical records.\n", len())

	// 3. Fetch Traffic: Views (Last 14 days)
	fmt.Println("Fetching Views...")
	, ,  := .Repositories.ListTrafficViews(, owner, repo, nil)
	if  != nil {
		log.Printf("Error fetching views: %v", )
	} else {
		for ,  := range .Views {
			 := .GetTimestamp().Format("2006-01-02")
			 := getOrCreate(, )
			.Views = .GetCount()
			.UniqueViews = .GetUniques()
		}
	}

	// 4. Fetch Traffic: Clones (Last 14 days)
	fmt.Println("Fetching Clones...")
	, ,  := .Repositories.ListTrafficClones(, owner, repo, nil)
	if  != nil {
		log.Printf("Error fetching clones: %v", )
	} else {
		for ,  := range .Clones {
			 := .GetTimestamp().Format("2006-01-02")
			 := getOrCreate(, )
			.Clones = .GetCount()
			.UniqueClones = .GetUniques()
		}
	}

	// 5. Fetch Release Downloads (Snapshot)
	// TODO wrong number (min 2 per each)
	 := 0
	// We assign the current total of *all* releases to "Today's" entry
	// fmt.Println("Fetching Release Downloads...")
	// opts := &github.ListOptions{PerPage: 100}
	// for {
	// 	releases, resp, err := client.Repositories.ListReleases(ctx, owner, repo, opts)
	// 	if err != nil {
	// 		log.Printf("Error fetching releases: %v", err)
	// 		break
	// 	}
	// 	for _, r := range releases {
	// 		for _, asset := range r.Assets {
	// 			totalDownloads += asset.GetDownloadCount()
	// 		}
	// 	}
	// 	if resp.NextPage == 0 {
	// 		break
	// 	}
	// 	opts.Page = resp.NextPage
	// }

	// Update Today's record with the cumulative download count
	 := time.Now().Format("2006-01-02")
	 := getOrCreate(, )
	.ReleaseDownloads = 

	// 6. Save merged data back to CSV
	if  := saveCSV(csvFilename, );  != nil {
		log.Fatalf("Failed to save CSV: %v", )
	}

	fmt.Printf("Done! Stats merged and saved to %s\n", csvFilename)
}

// Helper to get an entry from the map or create it if missing
func getOrCreate( map[string]*DailyStat,  string) *DailyStat {
	if ,  := [];  {
		return 
	}
	 := &DailyStat{Date: }
	[] = 
	return 
}

// Reads the existing CSV and returns a map
func loadExistingCSV( string) map[string]*DailyStat {
	 := make(map[string]*DailyStat)

	if path.Dir() != "" {
		 := os.MkdirAll(path.Dir(), 0755)
		if  != nil {
			panic( )
		}
	}

	,  := os.Open()
	if os.IsNotExist() {
		return  // Return empty map if file doesn't exist
	}
	if  != nil {
		log.Printf("Could not open existing CSV: %v", )
		return 
	}
	defer .Close()

	 := csv.NewReader()
	,  := .ReadAll()
	if  != nil {
		return 
	}

	// Skip header (row 0)
	for ,  := range  {
		if  == 0 || len() < 6 {
			continue
		}
		// Parse columns: Date, Views, UniqueViews, Clones, UniqueClones, ReleaseDL
		,  := strconv.Atoi([1])
		,  := strconv.Atoi([2])
		,  := strconv.Atoi([3])
		,  := strconv.Atoi([4])
		,  := strconv.Atoi([5])

		[[0]] = &DailyStat{
			Date:             [0],
			Views:            ,
			UniqueViews:      ,
			Clones:           ,
			UniqueClones:     ,
			ReleaseDownloads: ,
		}
	}
	return 
}

// Sorts the map by date and writes to CSV
func saveCSV( string,  map[string]*DailyStat) error {
	// 1. Convert map to slice for sorting
	var  []*DailyStat
	for ,  := range  {
		 = append(, )
	}

	// 2. Sort by Date
	sort.Slice(, func(,  int) bool {
		return [].Date < [].Date
	})

	// 3. Create File
	,  := os.Create()
	if  != nil {
		return 
	}
	defer .Close()

	 := csv.NewWriter()
	defer .Flush()

	// 4. Write Header
	 := []string{"Date", "Views", "Unique_Views", "Clones", "Unique_Clones", "Total_Release_Downloads"}
	if  := .Write();  != nil {
		return 
	}

	// 5. Write Rows
	for ,  := range  {
		 := []string{
			.Date,
			strconv.Itoa(.Views),
			strconv.Itoa(.UniqueViews),
			strconv.Itoa(.Clones),
			strconv.Itoa(.UniqueClones),
			strconv.Itoa(.ReleaseDownloads),
		}
		if  := .Write();  != nil {
			return 
		}
	}

	return nil
}

// ///// ///// /////

// ///// CHARTS

// ///// ///// /////

func genCharts( context.Context) {
	// 1. Read the CSV file
	// Assuming the file is named "repo_stats.csv"
	,  := os.Open(csvFilename)
	if  != nil {
		log.Fatalf("Unable to read input file: %v", )
	}
	defer .Close()

	 := csv.NewReader()
	,  := .ReadAll()
	if  != nil {
		log.Fatalf("Unable to parse file as CSV: %v", )
	}

	// 2. Process Data
	var  []string
	var  []opts.LineData

	// Loop through records, skipping header (index 0)
	for ,  := range  {
		if  == 0 {
			continue // Skip Header
		}

		// Safety check for column count
		if len() < 5 {
			continue
		}

		 := [0]
		// Skip rows with empty dates (like the ",0,0,0,0,0" row in your example)
		if  == "" {
			continue
		}

		// Parse Unique_Clones (Column Index 4)
		// CSV Format: Date,Views,Unique_Views,Clones,Unique_Clones,Total_Release_Downloads
		,  := strconv.Atoi([4])
		if  != nil {
			log.Printf("Skipping row %d due to invalid number: %v", , )
			continue
		}

		 = append(, )
		 = append(, opts.LineData{Value: })
	}

	 := []string{"light", "dark"}

	for ,  := range  {
		// 3. Create the Chart
		 := charts.NewLine()

		// Set global options (Titles, Legend, Tooltips)
		.SetGlobalOptions(
			charts.WithInitializationOpts(opts.Initialization{
				Theme: , // Optional: Use a nice theme
				Width: "800px",
			}),
			charts.WithTooltipOpts(opts.Tooltip{
				Show:    opts.Bool(true),
				Trigger: "axis",
			}),
			charts.WithXAxisOpts(opts.XAxis{
				Name: "Date",
			}),
			charts.WithYAxisOpts(opts.YAxis{
				Name: "Count",
			}),
		)

		// Set X-Axis and Add Series
		.SetXAxis().
			AddSeries("Unique Clones", ).
			SetSeriesOptions(
				charts.WithLineChartOpts(opts.LineChart{
					Smooth: opts.Bool(true), // Makes the line curved instead of jagged
				}),
				charts.WithAnimationOpts(opts.Animation{
					Animation: opts.Bool(false),
				}),
				charts.WithLabelOpts(opts.Label{
					Show: opts.Bool(true), // Show numbers on the dots
				}),
			)

		// 4. Save to HTML
		,  := os.Create("data/stats." +  + ".html")
		 = .Render()
		if  != nil {
			log.Println()
		}
		.Close()
	}
}

func render(,  string) {
	// 1. Setup the input and output file paths
	 := "data/stats." +  + ".html" // Ensure this file exists
	 := "data/stats." +  + ".png"

	// Get absolute path for the HTML file (required for file:// protocol)
	,  := os.Getwd()
	if  != nil {
		log.Fatal()
	}
	 := "file://" + filepath.Join(, )

	// 2. Create a Chromedp context
	// This starts a headless Chrome instance
	,  := chromedp.NewContext(context.Background())
	defer ()

	// 3. Define the tasks
	var  []byte
	fmt.Println("Capturing screenshot... (this may take a few seconds)")

	 = chromedp.Run(,
		// Navigate to the local HTML file
		chromedp.Navigate(),

		// Wait for the charts to render.
		// We look for the canvas tag which echarts generates.
		chromedp.WaitVisible("canvas", chromedp.ByQuery),
		chromedp.Evaluate("document.body.style.backgroundColor = '"++"'", nil),

		// IMPORTANT: Echarts has a startup animation.
		// We sleep briefly to ensure the lines are fully drawn before snapping.
		chromedp.Sleep(2*time.Second),

		// Capture the full page (or specific element)
		chromedp.FullScreenshot(&, 90),
	)

	if  != nil {
		log.Fatalf("Error taking screenshot: %v", )
	}

	// 4. Write the image buffer to a file
	if  := os.WriteFile(, , 0644);  != nil {
		log.Fatal()
	}

	fmt.Printf("Success! Saved image to %s\n", )
}