package usb

import (
	"fmt"
	"sync"
)

// Externally registered devices.
//
// Normally devices are found by walking sysfs. That is not available to an
// unprivileged Android app, which must go through the framework: it enumerates
// devices itself, asks the user for permission, and receives an already-open
// file descriptor plus the raw descriptor blob. Those devices are registered
// here and merged into the enumeration, so everything above this layer works
// the same whether a device came from sysfs or from outside.
var (
	externalMu      sync.RWMutex
	externalDevices = make(map[string]Device)
)

// RegisterExternalDevice adds a device that was discovered outside this
// process. descriptors is the raw blob (device descriptor followed by
// configuration descriptors), exactly what a usbdevfs file read returns and
// what Android's UsbDeviceConnection.getRawDescriptors() provides.
func RegisterExternalDevice(busID string, descriptors []byte, meta ExternalDeviceMeta) error {
	if busID == "" {
		return fmt.Errorf("bus ID is required")
	}

	parsed, err := ParseDescriptors(descriptors)
	if err != nil {
		return fmt.Errorf("parsing descriptors for %s: %w", busID, err)
	}

	cfg := parsed.FindConfig(meta.ConfigValue)
	if cfg == nil {
		cfg = &parsed.Configs[0]
	}

	dev := Device{
		BusID:          busID,
		BusNum:         meta.BusNum,
		DevNum:         meta.DevNum,
		Speed:          meta.Speed,
		VendorID:       parsed.VendorID,
		ProductID:      parsed.ProductID,
		BcdDevice:      parsed.BcdDevice,
		DeviceClass:    parsed.DeviceClass,
		DeviceSubClass: parsed.DeviceSubClass,
		DeviceProtocol: parsed.DeviceProtocol,
		ConfigValue:    cfg.Value,
		NumConfigs:     parsed.NumConfigs,
		Manufacturer:   meta.Manufacturer,
		Product:        meta.Product,
		Serial:         meta.Serial,
		Interfaces:     cfg.ActiveInterfaces(),
		Endpoints:      cfg.AllEndpoints(),
	}

	externalMu.Lock()
	externalDevices[busID] = dev
	externalMu.Unlock()

	return nil
}

// ExternalDeviceMeta carries the fields that cannot be read from the
// descriptor blob because they describe the device's place on the bus or come
// from string descriptors the caller already resolved.
type ExternalDeviceMeta struct {
	BusNum       uint32
	DevNum       uint32
	Speed        uint32
	ConfigValue  uint8
	Manufacturer string
	Product      string
	Serial       string
}

// UnregisterExternalDevice removes a device registered from outside.
func UnregisterExternalDevice(busID string) {
	externalMu.Lock()
	delete(externalDevices, busID)
	externalMu.Unlock()
}

// ExternalDevices returns a snapshot of the externally registered devices.
func ExternalDevices() []Device {
	externalMu.RLock()
	defer externalMu.RUnlock()

	result := make([]Device, 0, len(externalDevices))
	for _, dev := range externalDevices {
		result = append(result, dev)
	}
	return result
}

// HasExternalDevices reports whether any device came from outside.
func HasExternalDevices() bool {
	externalMu.RLock()
	defer externalMu.RUnlock()
	return len(externalDevices) > 0
}

// mergeExternal appends externally registered devices to a list from sysfs,
// letting the external entry win on a bus ID collision — it carries a file
// descriptor we can actually use, which the sysfs entry may not.
func mergeExternal(devices []Device) []Device {
	externalMu.RLock()
	defer externalMu.RUnlock()

	if len(externalDevices) == 0 {
		return devices
	}

	result := make([]Device, 0, len(devices)+len(externalDevices))
	for _, dev := range devices {
		if _, overridden := externalDevices[dev.BusID]; !overridden {
			result = append(result, dev)
		}
	}
	for _, dev := range externalDevices {
		result = append(result, dev)
	}
	return result
}
