package metrics

import (
	"slices"
	"strconv"
	"strings"
)

// OtherFormatLabel — значение метки для всего, чего нет в перечне known-форматов.
const OtherFormatLabel = "other"

// knownFormats — закрытый перечень расширений, которые допускаются меткой.
//
// Состав: форматы, доезжающие приёмом по HTTP, плюс собственное умолчание
// сервиса на случай имени без расширения. Значения `oga` и `mp4` достались от
// убранного входа Telegram — голосовое приходило оттуда как `voice/file_N.oga`,
// кружок с `.mp4`, — и остаются: перечень сужает **метку**, а не приём, и
// выброшенное из него значение уронило бы прежние записи в `other`.
var knownFormats = map[string]struct{}{
	"mp3":   {},
	"wav":   {},
	"ogg":   {},
	"oga":   {},
	"opus":  {},
	"flac":  {},
	"m4a":   {},
	"aac":   {},
	"wma":   {},
	"mp4":   {},
	"mkv":   {},
	"mov":   {},
	"avi":   {},
	"webm":  {},
	"audio": {}, // умолчание сервиса, когда расширения в имени не было
}

// PublicFormats — тот же перечень, но для подсказки диалогу выбора файла в
// приложении: **без** собственного умолчания сервиса.
//
// Второй перечень рядом с первым разошёлся бы с ним молча, поэтому источник
// один. `audio` из него вычтено: это не формат, а умолчание на случай имени без
// расширения, и подсказкой человеку оно выходить не должно.
//
// Сервис по этому перечню **не судит**: приём о годности записи не решает сам —
// расширение он берёт из имени файла, а пригодность содержимого узнаёт у
// источника метаданных. Перечень служит диалогу выбора файла, не более; норму
// держит capability `archive`.
func PublicFormats() []string {
	out := make([]string, 0, len(knownFormats))
	for format := range knownFormats {
		if format == defaultServiceFormat {
			continue
		}
		out = append(out, format)
	}
	slices.Sort(out)
	return out
}

// defaultServiceFormat — собственное умолчание сервиса на случай имени без
// расширения. Форматом не является.
const defaultServiceFormat = "audio"

// FormatLabel приводит расширение к виду, годному для метки метрики.
//
// Расширение приходит из имени, которое дал отправитель, и потому может быть
// чем угодно: имя `запись.тайное-слово` отдаёт `тайное-слово`. Страница метрик
// открыта, то есть метка — поверхность пошире журнала. Незнакомое значение
// заменяется одним общим: это закрывает и утечку куска имени, и рост числа
// временных рядов, которым иначе распоряжается анонимный отправитель.
//
// Имени файла на диске это не касается — там расширение остаётся пришедшим.
// ObserveInputFileSize записывает размер принятой записи. Расширение приводится
// здесь, а не у вызывающего: сырая точка употребления — это место, где хвост
// имени отправителя однажды снова уедет наружу, и проверка у вызывающего этого
// не заметит.
func ObserveInputFileSize(ext string, size int64) {
	InputFileSizeHistogram.WithLabelValues(FormatLabel(ext)).Observe(float64(size))
}

// ObserveConversionDuration записывает длительность конвертации. Исходный формат
// приводится по той же причине, что и в приёме.
func ObserveConversionDuration(srcExt, targetFormat string, failed bool, seconds float64) {
	ConversionDurationHistogram.
		WithLabelValues(FormatLabel(srcExt), targetFormat, strconv.FormatBool(failed)).
		Observe(seconds)
}

func FormatLabel(ext string) string {
	normalized := strings.ToLower(strings.TrimPrefix(ext, "."))
	if _, ok := knownFormats[normalized]; ok {
		return normalized
	}
	return OtherFormatLabel
}
