// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// CPAL-like host/device surface for macOS (CoreAudio).
///
/// Includes device enumeration, config queries and native stream I/O (AudioQueue) with
/// MoonBit callback bridging.
pub struct Host {
  _unit : Unit
} derive(Debug, Eq)

///|
pub struct Device {
  id : DeviceId
} derive(Debug, Eq)

///|
pub fn default_host() -> Host {
  { _unit: () }
}

///|
pub fn Host::devices(_self : Host) -> Array[Device] raise CoreAudioError {
  device_ids().map(fn(id) { { id, } })
}

///|
pub fn Host::default_output_device(_self : Host) -> Device? {
  match default_output_device_id() {
    Some(id) => Some({ id, })
    None => None
  }
}

///|
pub fn Host::default_input_device(_self : Host) -> Device? {
  match default_input_device_id() {
    Some(id) => Some({ id, })
    None => None
  }
}

///|
pub fn Device::name(self : Device) -> String raise CoreAudioError {
  device_name(self.id)
}

///|
pub fn Device::id(self : Device) -> @core.DeviceId {
  match device_uid(self.id) {
    Some(uid) if uid.trim().length() > 0 => DeviceId(CoreAudio, uid)
    // Keep backend parity with upstream: ID is the CoreAudio UID, not the numeric AudioDeviceID.
    // An empty ID is promoted to DeviceIdError by platform.Device::id.
    _ => DeviceId(CoreAudio, "")
  }
}

///|
pub fn Device::supports_input(self : Device) -> Bool {
  ca_input_channel_count(self.id) > 0
}

///|
pub fn Device::supports_output(self : Device) -> Bool {
  ca_output_channel_count(self.id) > 0
}

///|
pub fn Device::description(
  self : Device,
) -> @core.DeviceDescription raise CoreAudioError {
  let name = device_name(self.id)
  let input_count : Int? = try supported_input_configs(self.id) catch {
    _ => None
  } noraise {
    xs => Some(xs.length())
  }
  let output_count : Int? = try supported_output_configs(self.id) catch {
    _ => None
  } noraise {
    xs => Some(xs.length())
  }
  let direction = @core.direction_from_counts(input_count, output_count)
  let mut builder = @core.DeviceDescriptionBuilder(name).direction(direction)
  // Mirrors upstream CPAL: only tag aggregate devices.
  if device_is_aggregate(self.id) {
    builder = builder.interface_type(@core.InterfaceType::aggregate())
  }
  builder.build()
}

///|
pub fn Device::supported_input_configs(
  self : Device,
) -> Array[@core.SupportedStreamConfigRange] raise CoreAudioError {
  supported_input_configs(self.id)
}

///|
pub fn Device::supported_output_configs(
  self : Device,
) -> Array[@core.SupportedStreamConfigRange] raise CoreAudioError {
  supported_output_configs(self.id)
}

///|
pub fn Device::default_input_config(
  self : Device,
) -> @core.SupportedStreamConfig raise @core.DefaultStreamConfigError {
  default_input_config(self.id)
}

///|
pub fn Device::default_output_config(
  self : Device,
) -> @core.SupportedStreamConfig raise @core.DefaultStreamConfigError {
  default_output_config(self.id)
}