1package main
  2
  3import (
  4	"io"
  5	"log"
  6	"runtime"
  7	"sort"
  8	"sync"
  9	"time"
 10
 11	"github.com/go-enry/go-enry/v2"
 12	"github.com/go-git/go-git/v5/plumbing/object"
 13)
 14
 15var languageColors = map[string]string{
 16	"1C Enterprise":                "#814CCC",
 17	"ActionScript":                 "#882B0F",
 18	"Ada":                          "#02f88c",
 19	"Agda":                         "#315665",
 20	"AGS Script":                   "#B9D9FF",
 21	"Alloy":                        "#64C800",
 22	"AMPL":                         "#E6EFBB",
 23	"Ant Build System":             "#A9157E",
 24	"ANTLR":                        "#9DC3FF",
 25	"ApacheConf":                   "#d12127",
 26	"Apex":                         "#1797c0",
 27	"API Blueprint":                "#2ACCA8",
 28	"APL":                          "#5A8164",
 29	"AppleScript":                  "#101F1F",
 30	"Arc":                          "#aa2afe",
 31	"Arduino":                      "#bd7911",
 32	"ASP":                          "#6a40fd",
 33	"AspectJ":                      "#a957b0",
 34	"Assembly":                     "#6E4C13",
 35	"ATS":                          "#1ac620",
 36	"Augeas":                       "#9CC134",
 37	"AutoHotkey":                   "#6594b9",
 38	"AutoIt":                       "#1C3552",
 39	"Awk":                          "#c30e9b",
 40	"Ballerina":                    "#FF5000",
 41	"Batchfile":                    "#C1F12E",
 42	"Befunge":                      "#2F2530",
 43	"Bicep":                        "#519aba",
 44	"Bison":                        "#6A463F",
 45	"BitBake":                      "#00bce4",
 46	"Blade":                        "#f7523f",
 47	"BlitzBasic":                   "#00FFAE",
 48	"BlitzMax":                     "#cd6400",
 49	"Bluespec":                     "#12223c",
 50	"Boo":                          "#d4bec1",
 51	"Brainfuck":                    "#2F2530",
 52	"Brightscript":                 "#662D91",
 53	"C":                            "#555555",
 54	"C#":                           "#178600",
 55	"C++":                          "#f34b7d",
 56	"C3":                           "#2563eb",
 57	"Caddyfile":                    "#22b638",
 58	"Cairo":                        "#ff4a48",
 59	"Ceylon":                       "#dfa535",
 60	"Chapel":                       "#8dc63f",
 61	"ChucK":                        "#3f8000",
 62	"Cirru":                        "#ccccff",
 63	"Clarion":                      "#db901e",
 64	"Clean":                        "#3F85AF",
 65	"Click":                        "#E4E6F3",
 66	"CLIPS":                        "#00A300",
 67	"Clojure":                      "#db5855",
 68	"CMake":                        "#DA3434",
 69	"COBOL":                        "#1d2021",
 70	"CodeQL":                       "#140f46",
 71	"CoffeeScript":                 "#244776",
 72	"ColdFusion":                   "#ed2cd6",
 73	"Common Lisp":                  "#3fb68b",
 74	"Component Pascal":             "#B0CE4E",
 75	"Crystal":                      "#000100",
 76	"CSON":                         "#244776",
 77	"Csound":                       "#1a1a1a",
 78	"CSS":                          "#563d7c",
 79	"Cuda":                         "#3A4E3A",
 80	"Curry":                        "#531242",
 81	"Cycript":                      "#000000",
 82	"Cython":                       "#fedf5b",
 83	"D":                            "#ba595e",
 84	"Dart":                         "#00B4AB",
 85	"DataWeave":                    "#003a52",
 86	"Dhall":                        "#dfafff",
 87	"Dockerfile":                   "#384d54",
 88	"Dogescript":                   "#cca760",
 89	"DTrace":                       "#000000",
 90	"Dylan":                        "#6c616e",
 91	"E":                            "#ccce35",
 92	"eC":                           "#913960",
 93	"ECL":                          "#8a1267",
 94	"Eiffel":                       "#4d6977",
 95	"EJS":                          "#a91e50",
 96	"Elixir":                       "#6e4a7e",
 97	"Elm":                          "#60B5CC",
 98	"Emacs Lisp":                   "#c065db",
 99	"EmberScript":                  "#FFF4F3",
100	"EQ":                           "#a78649",
101	"Erlang":                       "#B83998",
102	"F#":                           "#b845fc",
103	"F*":                           "#572e30",
104	"Factor":                       "#636746",
105	"Fancy":                        "#7b9db4",
106	"Fantom":                       "#14253c",
107	"Faust":                        "#c37240",
108	"Fennel":                       "#fff3d7",
109	"fish":                         "#4aae47",
110	"FLUX":                         "#88ccff",
111	"Forth":                        "#341708",
112	"Fortran":                      "#4d41b1",
113	"FreeBASIC":                    "#141AC9",
114	"Frege":                        "#00cafe",
115	"Futhark":                      "#5f021f",
116	"G-code":                       "#D08CF2",
117	"Game Maker Language":          "#71b417",
118	"GAML":                         "#FFC766",
119	"GAMS":                         "#f49a22",
120	"GAP":                          "#0000cc",
121	"GDScript":                     "#355570",
122	"Genie":                        "#fb855d",
123	"Genshi":                       "#951531",
124	"Gentoo Ebuild":                "#9400ff",
125	"Gherkin":                      "#5B2063",
126	"Gleam":                        "#ffaff3",
127	"GLSL":                         "#5686a5",
128	"Glyph":                        "#c1ac7f",
129	"Gnuplot":                      "#f0a9f0",
130	"Go":                           "#00ADD8",
131	"Golo":                         "#88562A",
132	"Gosu":                         "#82937f",
133	"Grace":                        "#615f8b",
134	"Gradle":                       "#02303a",
135	"GraphQL":                      "#e10098",
136	"Groovy":                       "#4298b8",
137	"Hack":                         "#878787",
138	"Haml":                         "#ece2a9",
139	"Handlebars":                   "#f7931e",
140	"Harbour":                      "#0e60e3",
141	"Haskell":                      "#5e5086",
142	"Haxe":                         "#df7900",
143	"HCL":                          "#844FBA",
144	"HiveQL":                       "#dce200",
145	"HolyC":                        "#ffefaf",
146	"HTML":                         "#e34c26",
147	"Hy":                           "#7790B2",
148	"IDL":                          "#a3522f",
149	"Idris":                        "#b30000",
150	"Ignore List":                  "#000000",
151	"IGOR Pro":                     "#0000cc",
152	"Imba":                         "#16cec6",
153	"Inform 7":                     "#3d9970",
154	"INI":                          "#d1dbe0",
155	"Inno Setup":                   "#264b99",
156	"Io":                           "#a9188d",
157	"Ioke":                         "#078193",
158	"Isabelle":                     "#FEFE00",
159	"J":                            "#9EEDFF",
160	"Janet":                        "#0886a5",
161	"Java":                         "#b07219",
162	"JavaScript":                   "#f1e05a",
163	"Jinja":                        "#a52a22",
164	"Jison":                        "#56b3cb",
165	"Jolie":                        "#843179",
166	"JSON":                         "#292929",
167	"Jsonnet":                      "#0064bd",
168	"Julia":                        "#a270ba",
169	"Jupyter Notebook":             "#DA5B0B",
170	"Just":                         "#384d54",
171	"Kaitai Struct":                "#773b37",
172	"KCL":                          "#7ABABF",
173	"Kotlin":                       "#A97BFF",
174	"KRL":                          "#28430A",
175	"LabVIEW":                      "#fede06",
176	"Lasso":                        "#999999",
177	"Latte":                        "#f2a542",
178	"Lean":                         "#3d6117",
179	"Less":                         "#1d365d",
180	"Lex":                          "#DBCA00",
181	"LigoLANG":                     "#0e74ff",
182	"LilyPond":                     "#9ccc7c",
183	"Liquid":                       "#67b8de",
184	"LiveScript":                   "#499886",
185	"LLVM":                         "#185619",
186	"Logtalk":                      "#295b9a",
187	"LOLCODE":                      "#cc9900",
188	"LookML":                       "#652B81",
189	"LSL":                          "#3d9970",
190	"Lua":                          "#000080",
191	"Luau":                         "#00A2FF",
192	"M4":                           "#000000",
193	"Macaulay2":                    "#d8ffff",
194	"Makefile":                     "#427819",
195	"Markdown":                     "#083fa1",
196	"Marko":                        "#42bff2",
197	"Mask":                         "#f97732",
198	"MATLAB":                       "#e16737",
199	"Max":                          "#c4a79c",
200	"MAXScript":                    "#00a6a6",
201	"MDX":                          "#fcb32c",
202	"Mercury":                      "#ff2b2b",
203	"Meson":                        "#007800",
204	"Metal":                        "#8f14e9",
205	"MiniYAML":                     "#ff1111",
206	"Mint":                         "#02b046",
207	"Mirah":                        "#c7a938",
208	"Modelica":                     "#de1d31",
209	"Modula-2":                     "#10253f",
210	"Mojo":                         "#ff4c1f",
211	"MoonScript":                   "#ff4585",
212	"Move":                         "#4a137a",
213	"MQL4":                         "#62A8D6",
214	"MQL5":                         "#4A76B8",
215	"MTML":                         "#b7e1f4",
216	"Mustache":                     "#724b3b",
217	"Nemerle":                      "#3d3c6e",
218	"nesC":                         "#94B0C7",
219	"NetLinx":                      "#0aa0ff",
220	"NetLogo":                      "#ff6375",
221	"NewLisp":                      "#87AED7",
222	"Nextflow":                     "#3ac486",
223	"Nginx":                        "#009639",
224	"Nim":                          "#ffc200",
225	"Nit":                          "#009917",
226	"Nix":                          "#7e7eff",
227	"Nushell":                      "#4E9906",
228	"NWScript":                     "#111522",
229	"Objective-C":                  "#438eff",
230	"Objective-C++":                "#6866fb",
231	"Objective-J":                  "#ff0c5a",
232	"OCaml":                        "#ef7a08",
233	"Odin":                         "#60AFFE",
234	"Omgrofl":                      "#cabbff",
235	"ooc":                          "#b0b77e",
236	"Opal":                         "#f7ede0",
237	"Open Policy Agent":            "#7d9199",
238	"OpenCL":                       "#ed2e2d",
239	"OpenEdge ABL":                 "#5ce600",
240	"OpenQASM":                     "#AA70FF",
241	"OpenSCAD":                     "#e5cd45",
242	"Org":                          "#77aa99",
243	"Ox":                           "#000000",
244	"Oxygene":                      "#cdd0e3",
245	"Oz":                           "#fab738",
246	"P4":                           "#7055b5",
247	"Papyrus":                      "#660000",
248	"Parrot":                       "#f3ca0a",
249	"Pascal":                       "#E3F171",
250	"Pawn":                         "#dbb284",
251	"Pep8":                         "#C76F5B",
252	"Perl":                         "#0298c3",
253	"PHP":                          "#4f5d95",
254	"PicoLisp":                     "#6067af",
255	"PigLatin":                     "#fce7de",
256	"Pike":                         "#005390",
257	"PLpgSQL":                      "#336790",
258	"PLSQL":                        "#dad8d8",
259	"PogoScript":                   "#d80073",
260	"Polar":                        "#316880",
261	"Pony":                         "#000000",
262	"PostScript":                   "#da291c",
263	"PowerShell":                   "#012456",
264	"Prisma":                       "#0c344b",
265	"Processing":                   "#0096D8",
266	"Prolog":                       "#74283c",
267	"Promela":                      "#de3900",
268	"Protocol Buffer":              "#000000",
269	"Pug":                          "#a86454",
270	"Puppet":                       "#302B6D",
271	"PureBasic":                    "#5a6986",
272	"PureScript":                   "#1D222D",
273	"Python":                       "#3572A5",
274	"QMake":                        "#000000",
275	"QML":                          "#44a51c",
276	"Qt Script":                    "#00b0ff",
277	"Quake":                        "#882303",
278	"R":                            "#198CE7",
279	"Racket":                       "#3c5caa",
280	"Ragel":                        "#9d5200",
281	"Raku":                         "#0000fb",
282	"RAML":                         "#77d9fb",
283	"Razor":                        "#512be4",
284	"Rebol":                        "#358a5b",
285	"Red":                          "#ee0000",
286	"Redcode":                      "#000000",
287	"Ren'Py":                       "#ff7f7f",
288	"RenderScript":                 "#000000",
289	"Rescript":                     "#ed4e4e",
290	"REXX":                         "#d90e09",
291	"Ring":                         "#2D54CB",
292	"Riot":                         "#A71E22",
293	"RMarkdown":                    "#198ce7",
294	"RobotFramework":               "#00c0b5",
295	"Roff":                         "#ecdebe",
296	"Rouge":                        "#cc0000",
297	"Ruby":                         "#701516",
298	"RUNOFF":                       "#660000",
299	"Rust":                         "#dea584",
300	"Sage":                         "#000000",
301	"SaltStack":                    "#646464",
302	"SAS":                          "#B34936",
303	"Sass":                         "#a53b70",
304	"Scala":                        "#c22d40",
305	"Scaml":                        "#bd181a",
306	"Scheme":                       "#1e4aec",
307	"Scilab":                       "#ca0f21",
308	"SCSS":                         "#c6538c",
309	"sed":                          "#64b970",
310	"Self":                         "#0579aa",
311	"ShaderLab":                    "#222c37",
312	"Shell":                        "#89e051",
313	"Shen":                         "#120F14",
314	"Sieve":                        "#000000",
315	"Slash":                        "#007eff",
316	"Slice":                        "#003fa2",
317	"Slim":                         "#2b2b2b",
318	"Smali":                        "#000000",
319	"Smalltalk":                    "#596706",
320	"Smarty":                       "#f0c040",
321	"Smithy":                       "#c44536",
322	"SmPL":                         "#c92223",
323	"Solidity":                     "#AA6746",
324	"SourcePawn":                   "#f69e1d",
325	"SPARQL":                       "#0C4597",
326	"SQF":                          "#3F3F3F",
327	"SQL":                          "#e38c00",
328	"SQLPL":                        "#e38c00",
329	"Squirrel":                     "#800000",
330	"SRecode Template":             "#348a34",
331	"Stan":                         "#b2011d",
332	"Standard ML":                  "#dc566d",
333	"Starlark":                     "#76d275",
334	"Stata":                        "#1a5f91",
335	"STL":                          "#373b3e",
336	"Stylus":                       "#ff6347",
337	"SuperCollider":                "#46390b",
338	"Svelte":                       "#ff3e00",
339	"SVG":                          "#ff9900",
340	"Swift":                        "#F05138",
341	"SWIG":                         "#000000",
342	"SystemVerilog":                "#DAE1C2",
343	"Tcl":                          "#e4cc98",
344	"Tcsh":                         "#000000",
345	"Terra":                        "#000000",
346	"TeX":                          "#3D6117",
347	"Thrift":                       "#D88E35",
348	"TI Program":                   "#A0AAAD",
349	"TLA":                          "#4b0082",
350	"TOML":                         "#9c4221",
351	"TSQL":                         "#e38c00",
352	"TSX":                          "#3178c6",
353	"Turing":                       "#cf142b",
354	"Turtle":                       "#EEFF11",
355	"Twig":                         "#c1d026",
356	"TXL":                          "#0178b8",
357	"TypeScript":                   "#3178c6",
358	"Typst":                        "#239dad",
359	"Unified Parallel C":           "#4e3617",
360	"Unity3D Asset":                "#222c37",
361	"Uno":                          "#9933cc",
362	"UnrealScript":                 "#a54c4d",
363	"UrWeb":                        "#ccc",
364	"V":                            "#4f87c4",
365	"Vala":                         "#fbe5cd",
366	"Valve Data Format":            "#f26025",
367	"VBA":                          "#867db1",
368	"VBScript":                     "#15dcdc",
369	"VCL":                          "#148AA8",
370	"Verilog":                      "#b2b7f8",
371	"VHDL":                         "#adb2cb",
372	"Vim Help File":                "#199f4b",
373	"Vim Script":                   "#199f4b",
374	"Visual Basic .NET":            "#9400ff",
375	"Volt":                         "#1F1F1F",
376	"Vue":                          "#41b883",
377	"Vyper":                        "#2980b9",
378	"WDL":                          "#42f1f4",
379	"WebAssembly":                  "#04133b",
380	"WebIDL":                       "#000000",
381	"Whiley":                       "#d5c397",
382	"Wikitext":                     "#fc5757",
383	"Windows Registry Entries":     "#52a5df",
384	"Witcher Script":               "#ff0000",
385	"Wollok":                       "#a23738",
386	"World of Warcraft Addon Data": "#f7e43a",
387	"Wren":                         "#383838",
388	"X10":                          "#4B6BEF",
389	"xBase":                        "#403a40",
390	"XC":                           "#99FF33",
391	"XML":                          "#0060ac",
392	"XML Property List":            "#0060ac",
393	"Xojo":                         "#81bd41",
394	"Xonsh":                        "#285880",
395	"XOTL":                         "#000000",
396	"XQuery":                       "#5232e7",
397	"XSLT":                         "#EB8E35",
398	"Xtend":                        "#24255d",
399	"Yacc":                         "#4B6C4B",
400	"YAML":                         "#cb171e",
401	"YANG":                         "#000000",
402	"YARA":                         "#220000",
403	"YASnippet":                    "#32AB90",
404	"ZAP":                          "#0d6616",
405	"Zeek":                         "#000000",
406	"ZenScript":                    "#00BCD1",
407	"Zephir":                       "#118f9e",
408	"Zig":                          "#ec915c",
409	"ZIL":                          "#dc75e5",
410	"Zimpl":                        "#d67711",
411	"Tree-sitter Query":            "#9440ff",
412}
413
414func getLanguageStats(repoName string, commitHash string, tree *object.Tree) ([]LanguageStat, error) {
415	key := LangCacheKey{RepoName: repoName, CommitHash: commitHash}
416
417	if val, ok := langCache.Load(key); ok {
418		log.Printf("OPS Language stats cache hit: %s [%s]", repoName, commitHash)
419		return val.([]LanguageStat), nil
420	}
421
422	log.Printf("OPS Calculating language stats: %s [%s]", repoName, commitHash)
423	start := time.Now()
424
425	type result struct {
426		lang string
427		size int64
428	}
429
430	numWorkers := runtime.NumCPU()
431	filesChan := make(chan *object.File, numWorkers*2)
432	resultsChan := make(chan result, numWorkers*2)
433	var wg sync.WaitGroup
434
435	for i := 0; i < numWorkers; i++ {
436		wg.Add(1)
437		go func() {
438			defer wg.Done()
439			for f := range filesChan {
440				if enry.IsVendor(f.Name) || enry.IsDotFile(f.Name) || enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
441					continue
442				}
443
444				// Fast path: detect by extension
445				lang, safe := enry.GetLanguageByExtension(f.Name)
446				if safe && lang != "" && lang != enry.OtherLanguage {
447					resultsChan <- result{lang: lang, size: f.Size}
448					continue
449				}
450
451				// Slow path: detect by content (read only first 16KB)
452				reader, err := f.Reader()
453				if err != nil {
454					continue
455				}
456
457				content := make([]byte, 16384)
458				n, err := reader.Read(content)
459				reader.Close()
460				if err != nil && err != io.EOF {
461					continue
462				}
463				content = content[:n]
464
465				if enry.IsBinary(content) {
466					continue
467				}
468
469				lang = enry.GetLanguage(f.Name, content)
470				if lang != "" && lang != enry.OtherLanguage {
471					resultsChan <- result{lang: lang, size: f.Size}
472				}
473			}
474		}()
475	}
476
477	languages := make(map[string]int64)
478	var totalSize int64
479	done := make(chan struct{})
480	go func() {
481		for res := range resultsChan {
482			languages[res.lang] += res.size
483			totalSize += res.size
484		}
485		close(done)
486	}()
487
488	err := tree.Files().ForEach(func(f *object.File) error {
489		filesChan <- f
490		return nil
491	})
492	close(filesChan)
493	wg.Wait()
494	close(resultsChan)
495	<-done
496
497	if err != nil {
498		return nil, err
499	}
500
501	var stats []LanguageStat
502	for name, size := range languages {
503		percentage := (float64(size) / float64(totalSize)) * 100
504		if percentage < 0.1 {
505			continue
506		}
507		stats = append(stats, LanguageStat{
508			Name:       name,
509			Size:       size,
510			Percentage: percentage,
511			Color:      getLanguageColor(name),
512		})
513	}
514
515	sort.Slice(stats, func(i, j int) bool {
516		return stats[i].Size > stats[j].Size
517	})
518
519	var currentOffset float64
520	for i := range stats {
521		stats[i].Offset = currentOffset
522		currentOffset += stats[i].Percentage
523	}
524
525	langCache.Store(key, stats)
526	NotifySave()
527
528	log.Printf("OPS Language stats calculated for %s [%s] in %v", repoName, commitHash, time.Since(start))
529
530	return stats, nil
531}
532
533func getLanguageColor(lang string) string {
534	if color, ok := languageColors[lang]; ok {
535		return color
536	}
537	return "#8b8b8b"
538}