// 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.

///|
/// WASAPI backend (Windows).
///
/// This backend provides device enumeration, default/supported configs (best-effort), and
/// real stream I/O via a native C implementation (callback-thread model).

///|
pub struct Host {
  _unit : Unit
} derive(Debug, Eq)

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

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

///|
pub fn Host::is_available(_self : Host) -> Bool {
  @core.native_os() == Windows
}

///|
pub fn Host::devices(_self : Host) -> Array[Device] {
  if @core.native_os() == Windows {
    let bytes = devices_utf8()
    let s = @core.utf8_bytes_to_string(bytes, bytes.length())
    let out : Array[Device] = []
    for v in s[:].split("\n"[:]) {
      let id = v.trim().to_owned()
      if id.length() > 0 {
        out.push({ id, })
      }
    }
    out
  } else {
    []
  }
}

///|
pub fn Host::default_output_device(_self : Host) -> Device? {
  if @core.native_os() == Windows {
    match default_device_id_utf8(false) {
      None => None
      Some(id) => Some({ id, })
    }
  } else {
    None
  }
}

///|
pub fn Host::default_input_device(_self : Host) -> Device? {
  if @core.native_os() == Windows {
    match default_device_id_utf8(true) {
      None => None
      Some(id) => Some({ id, })
    }
  } else {
    None
  }
}

///|
pub fn Device::name(_self : Device) -> String {
  if @core.native_os() != Windows {
    return _self.id
  }
  match device_name_utf8(_self.id) {
    None => _self.id
    Some(s) => s
  }
}

///|
fn wasapi_ascii_upper(s : String) -> String {
  let b = Bytes::makei(s.length(), fn(i) {
    let cu = s.code_unit_at(i).to_int()
    if cu >= 97 && cu <= 122 { // a-z
      (cu - 32).to_byte()
    } else {
      cu.to_byte()
    }
  })
  @core.utf8_bytes_to_string(b, b.length())
}

///|
fn wasapi_interface_type_from_enumerator(
  enumerator_name : String,
) -> @core.InterfaceType? {
  match wasapi_ascii_upper(enumerator_name.trim().to_owned()) {
    "HDAUDIO" => Some(@core.InterfaceType::built_in())
    "USB" => Some(@core.InterfaceType::usb())
    "BTHENUM" => Some(@core.InterfaceType::bluetooth())
    "MMDEVAPI" | "SW" => Some(@core.InterfaceType::virtual_())
    _ => None
  }
}

///|
fn wasapi_interface_type_from_jack_subtype(
  jack_subtype : String,
) -> @core.InterfaceType? {
  match wasapi_ascii_upper(jack_subtype.trim().to_owned()) {
    "{D9E55EA0-0C89-4692-84FF-EB3C4B0D172F}" =>
      Some(@core.InterfaceType::hdmi())
    "{E47E4031-3EA6-418D-8F9B-B73843CCB2AD}" =>
      Some(@core.InterfaceType::display_port())
    "{DFF21CE1-F70F-11D0-B917-00A0C9223196}" =>
      Some(@core.InterfaceType::spdif())
    _ => None
  }
}

///|
fn wasapi_types_from_form_factor(
  form_factor : UInt,
) -> (@core.DeviceType, @core.InterfaceType?) {
  match form_factor.reinterpret_as_int() {
    // RemoteNetworkDevice
    0 => (@core.DeviceType::unknown(), Some(@core.InterfaceType::network()))
    // Speakers
    1 => (@core.DeviceType::speaker(), None)
    // LineLevel
    2 => (@core.DeviceType::unknown(), Some(@core.InterfaceType::line()))
    // Headphones
    3 => (@core.DeviceType::headphones(), None)
    // Microphone
    4 => (@core.DeviceType::microphone(), None)
    // Headset
    5 => (@core.DeviceType::headset(), None)
    // Handset
    6 => (@core.DeviceType::handset(), None)
    // UnknownDigitalPassthrough
    7 => (@core.DeviceType::unknown(), None)
    // SPDIF
    8 => (@core.DeviceType::unknown(), Some(@core.InterfaceType::spdif()))
    // DigitalAudioDisplayDevice
    9 => (@core.DeviceType::unknown(), Some(@core.InterfaceType::hdmi()))
    _ => (@core.DeviceType::unknown(), None)
  }
}

///|
fn wasapi_non_empty_trimmed(s : String?) -> String? {
  match s {
    Some(v) => {
      let t = v.trim().to_owned()
      if t.length() > 0 {
        Some(v)
      } else {
        None
      }
    }
    None => None
  }
}

///|
fn wasapi_description_name(
  dev_desc : String?,
  friendly : String?,
) -> String raise @core.DeviceNameError {
  let d = wasapi_non_empty_trimmed(dev_desc)
  let f = wasapi_non_empty_trimmed(friendly)
  match d {
    Some(v) => v
    None =>
      match f {
        Some(v) => v
        None =>
          raise @core.device_name_error_backend_specific(
            "failed to retrieve device name",
          )
      }
  }
}

///|
pub fn Device::description(
  _self : Device,
) -> @core.DeviceDescription raise @core.DeviceNameError {
  let dev_desc = device_property_utf8(_self.id, 2)
  let friendly = device_property_utf8(_self.id, 6)
  let name = wasapi_description_name(dev_desc, friendly)
  let direction = match data_flow_tag(_self.id) {
    Some(1) => @core.DeviceDirection::input()
    Some(2) => @core.DeviceDirection::output()
    _ => @core.DeviceDirection::unknown()
  }
  let interface_name = device_property_utf8(_self.id, 3)
  let enumerator_name = device_property_utf8(_self.id, 4)
  let jack_subtype = device_property_utf8(_self.id, 5)
  let (device_type, interface_type_seed) = match
    device_form_factor_u32(_self.id) {
    Some(ff) => wasapi_types_from_form_factor(ff)
    None => (@core.DeviceType::unknown(), None)
  }
  let mut interface_type_opt = interface_type_seed
  match enumerator_name {
    Some(s) =>
      match wasapi_interface_type_from_enumerator(s) {
        Some(it) => interface_type_opt = Some(it)
        None => ()
      }
    None => ()
  }
  match jack_subtype {
    Some(s) =>
      match wasapi_interface_type_from_jack_subtype(s) {
        Some(it) => interface_type_opt = Some(it)
        None => ()
      }
    None => ()
  }
  let mut builder = @core.DeviceDescriptionBuilder(name)
    .direction(direction)
    .device_type(device_type)
  match interface_type_opt {
    None => ()
    Some(it) => builder = builder.interface_type(it)
  }
  match interface_name {
    Some(s) if s.trim().length() > 0 => builder = builder.driver(s)
    _ => ()
  }
  // Mirrors upstream CPAL: add FriendlyName to `extended` only when we used DeviceDesc as name.
  match (dev_desc, friendly) {
    (Some(dd), Some(fname)) if dd.trim().length() > 0 &&
      fname.trim().length() > 0 &&
      fname != dd => builder = builder.add_extended_line(fname)
    _ => ()
  }
  builder.build()
}

///|
pub fn Device::id(_self : Device) -> @core.DeviceId {
  DeviceId(Wasapi, _self.id)
}

///|
pub fn Device::supports_input(_self : Device) -> Bool {
  match data_flow_tag(_self.id) {
    Some(1) => true
    _ => false
  }
}

///|
pub fn Device::supports_output(_self : Device) -> Bool {
  match data_flow_tag(_self.id) {
    Some(2) => true
    _ => false
  }
}

///|
fn clamp_u32_to_frame_count(u : UInt) -> Int {
  let lim = (2147483647 : Int).reinterpret_as_uint()
  if u <= lim {
    u.reinterpret_as_int()
  } else {
    2147483647
  }
}

///|
fn wasapi_sample_format_from_tag(fmt_tag : UInt) -> @core.SampleFormat? {
  match fmt_tag {
    1 => Some(F32)
    2 => Some(I16)
    3 => Some(U16)
    4 => Some(U8)
    6 => Some(I24)
    7 => Some(U24)
    8 => Some(I32)
    10 => Some(I64)
    _ => None
  }
}

///|
pub fn Device::supported_input_configs(
  _self : Device,
) -> Array[@core.SupportedStreamConfigRange] {
  if @core.native_os() != Windows {
    return []
  }
  match data_flow_tag(_self.id) {
    Some(2) => return []
    _ => ()
  }
  match supported_configs_u32(_self.id, true) {
    None =>
      match default_config_ex_u32(_self.id, true) {
        None => []
        Some((ch, sr, fmt_tag, bmin, bmax)) => {
          let fmt = match wasapi_sample_format_from_tag(fmt_tag) {
            Some(v) => v
            None => return []
          }
          let buf = @core.SupportedBufferSize::Range(
            min=clamp_u32_to_frame_count(bmin),
            max=clamp_u32_to_frame_count(bmax),
          )
          [
            SupportedStreamConfigRange(
              ch.reinterpret_as_int(),
              sr.reinterpret_as_int(),
              sr.reinterpret_as_int(),
              buf,
              fmt,
            ),
          ]
        }
      }
    Some((n, buf)) => {
      let out : Array[@core.SupportedStreamConfigRange] = []
      let count = if n < 0 { 0 } else { n }
      let max = if count > 64 { 64 } else { count }
      for i in 0.. v
          None => continue
        }
        let b = @core.SupportedBufferSize::Range(
          min=clamp_u32_to_frame_count(bmin),
          max=clamp_u32_to_frame_count(bmax),
        )
        out.push(
          SupportedStreamConfigRange(
            ch.reinterpret_as_int(),
            sr.reinterpret_as_int(),
            sr.reinterpret_as_int(),
            b,
            fmt,
          ),
        )
      }
      out
    }
  }
}

///|
pub fn Device::supported_output_configs(
  _self : Device,
) -> Array[@core.SupportedStreamConfigRange] {
  if @core.native_os() != Windows {
    return []
  }
  match data_flow_tag(_self.id) {
    Some(1) => return []
    _ => ()
  }
  match supported_configs_u32(_self.id, false) {
    None =>
      match default_config_ex_u32(_self.id, false) {
        None => []
        Some((ch, sr, fmt_tag, bmin, bmax)) => {
          let fmt = match wasapi_sample_format_from_tag(fmt_tag) {
            Some(v) => v
            None => return []
          }
          let buf = @core.SupportedBufferSize::Range(
            min=clamp_u32_to_frame_count(bmin),
            max=clamp_u32_to_frame_count(bmax),
          )
          [
            SupportedStreamConfigRange(
              ch.reinterpret_as_int(),
              sr.reinterpret_as_int(),
              sr.reinterpret_as_int(),
              buf,
              fmt,
            ),
          ]
        }
      }
    Some((n, buf)) => {
      let out : Array[@core.SupportedStreamConfigRange] = []
      let count = if n < 0 { 0 } else { n }
      let max = if count > 64 { 64 } else { count }
      for i in 0.. v
          None => continue
        }
        let b = @core.SupportedBufferSize::Range(
          min=clamp_u32_to_frame_count(bmin),
          max=clamp_u32_to_frame_count(bmax),
        )
        out.push(
          SupportedStreamConfigRange(
            ch.reinterpret_as_int(),
            sr.reinterpret_as_int(),
            sr.reinterpret_as_int(),
            b,
            fmt,
          ),
        )
      }
      out
    }
  }
}

///|
pub fn Device::default_input_config(
  _self : Device,
) -> @core.SupportedStreamConfig raise @core.DefaultStreamConfigError {
  if @core.native_os() != Windows {
    raise @core.default_stream_config_error_backend_specific(
      "wasapi default_input_config: not available on this OS",
    )
  }
  match data_flow_tag(_self.id) {
    Some(2) =>
      raise @core.default_stream_config_error_stream_type_not_supported()
    _ => ()
  }
  match default_config_ex_u32(_self.id, true) {
    None =>
      raise @core.default_stream_config_error_backend_specific(
        "wasapi default_input_config: query failed",
      )
    Some((ch, sr, fmt_tag, bmin, bmax)) => {
      let fmt = match wasapi_sample_format_from_tag(fmt_tag) {
        Some(v) => v
        None =>
          raise @core.default_stream_config_error_backend_specific(
            "wasapi default_input_config: unsupported format tag",
          )
      }
      SupportedStreamConfig(
        ch.reinterpret_as_int(),
        sr.reinterpret_as_int(),
        Range(
          min=clamp_u32_to_frame_count(bmin),
          max=clamp_u32_to_frame_count(bmax),
        ),
        fmt,
      )
    }
  }
}

///|
pub fn Device::default_output_config(
  _self : Device,
) -> @core.SupportedStreamConfig raise @core.DefaultStreamConfigError {
  if @core.native_os() != Windows {
    raise @core.default_stream_config_error_backend_specific(
      "wasapi default_output_config: not available on this OS",
    )
  }
  match data_flow_tag(_self.id) {
    Some(1) =>
      raise @core.default_stream_config_error_stream_type_not_supported()
    _ => ()
  }
  match default_config_ex_u32(_self.id, false) {
    None =>
      raise @core.default_stream_config_error_backend_specific(
        "wasapi default_output_config: query failed",
      )
    Some((ch, sr, fmt_tag, bmin, bmax)) => {
      let fmt = match wasapi_sample_format_from_tag(fmt_tag) {
        Some(v) => v
        None =>
          raise @core.default_stream_config_error_backend_specific(
            "wasapi default_output_config: unsupported format tag",
          )
      }
      SupportedStreamConfig(
        ch.reinterpret_as_int(),
        sr.reinterpret_as_int(),
        Range(
          min=clamp_u32_to_frame_count(bmin),
          max=clamp_u32_to_frame_count(bmax),
        ),
        fmt,
      )
    }
  }
}