// Plain Swift value types — what survives past the native struct/free-list lifetime
// (Marshaling.swift converts the C structs into these and immediately frees the native
// list). Nothing here holds a raw pointer. This is the Swift analog of the C# client's
// Models.cs. Field naming follows Swift camelCase (the C structs use snake_case).

import VoiceCatC

/// Channel snapshot — mirrors `vc_channel` (the pull-based view; re-call `listChannels()`
/// after `.channelList` / `.userJoined` / `.userLeft` / `.userUpdated` events).
public struct Channel: Sendable, Equatable, Identifiable {
    public let id: UInt32
    /// 0 = root.
    public let parentId: UInt32
    public let name: String
    public let topic: String
    public let passwordProtected: Bool
    /// 0 = unlimited.
    public let maxUsers: UInt32

    public init(id: UInt32, parentId: UInt32, name: String, topic: String,
                passwordProtected: Bool, maxUsers: UInt32) {
        self.id = id; self.parentId = parentId; self.name = name; self.topic = topic
        self.passwordProtected = passwordProtected; self.maxUsers = maxUsers
    }
}

/// Channel creation/edition descriptor — mirrors `vc_channel_info`. Used by
/// `createChannel(_:)` and `editChannel(_:)`. `id == 0` means new channel (for create).
public struct ChannelEdit: Sendable, Equatable {
    public let id: UInt32           // 0 = new channel for create
    public let parentId: UInt32     // 0 = root
    public let name: String
    public let topic: String
    public let passwordProtected: Bool
    public let password: String?    // nil/empty ignored if passwordProtected == false
    public let maxUsers: UInt32     // 0 = unlimited
    public let sortOrder: UInt32
    /// 0/nil fields use server defaults.
    public let audio: AudioConfig

    public init(id: UInt32, parentId: UInt32, name: String, topic: String,
                passwordProtected: Bool, password: String?, maxUsers: UInt32,
                sortOrder: UInt32, audio: AudioConfig) {
        self.id = id; self.parentId = parentId; self.name = name; self.topic = topic
        self.passwordProtected = passwordProtected; self.password = password
        self.maxUsers = maxUsers; self.sortOrder = sortOrder; self.audio = audio
    }
}

/// User snapshot — mirrors `vc_user`.
public struct User: Sendable, Equatable, Identifiable {
    public let id: UInt32
    public let nickname: String
    public let isGuest: Bool
    public let channelId: UInt32
    public let selfMicMuted: Bool
    public let selfDeafened: Bool
    public let serverMuted: Bool
    public let serverDeafened: Bool

    public init(id: UInt32, nickname: String, isGuest: Bool, channelId: UInt32,
                selfMicMuted: Bool, selfDeafened: Bool, serverMuted: Bool,
                serverDeafened: Bool) {
        self.id = id; self.nickname = nickname; self.isGuest = isGuest; self.channelId = channelId
        self.selfMicMuted = selfMicMuted; self.selfDeafened = selfDeafened
        self.serverMuted = serverMuted; self.serverDeafened = serverDeafened
    }
}

/// Permission bitset — mirrors `vc_permissions` (M5).
public struct Permissions: Sendable, Equatable {
    public let canCreateTempChannel: Bool
    public let canKick: Bool
    public let canBan: Bool
    public let canMoveUsers: Bool
    public let canAdminAccounts: Bool
    public let isAdmin: Bool

    public init(canCreateTempChannel: Bool, canKick: Bool, canBan: Bool,
                canMoveUsers: Bool, canAdminAccounts: Bool, isAdmin: Bool) {
        self.canCreateTempChannel = canCreateTempChannel; self.canKick = canKick; self.canBan = canBan
        self.canMoveUsers = canMoveUsers; self.canAdminAccounts = canAdminAccounts; self.isAdmin = isAdmin
    }
}

/// Account entry — mirrors `vc_account` (M5, reply to `listAccounts()`).
public struct Account: Sendable, Equatable {
    public let username: String
    public let isAdmin: Bool
    public let createdAtUnixMs: UInt64
    public let lastLoginUnixMs: UInt64

    public init(username: String, isAdmin: Bool, createdAtUnixMs: UInt64,
                lastLoginUnixMs: UInt64) {
        self.username = username; self.isAdmin = isAdmin
        self.createdAtUnixMs = createdAtUnixMs; self.lastLoginUnixMs = lastLoginUnixMs
    }
}

/// Per-user stream summary — mirrors `vc_stream_summary`. For the full effective Opus
/// config of a specific (user_id, stream_id), use `VoiceCatClient.getStreamAudioConfig`.
public struct StreamSummary: Sendable, Equatable, Identifiable {
    public let id: UInt32       // stream_id
    public let kind: VoiceCatStreamKind
    public let label: String

    public init(streamId: UInt32, kind: VoiceCatStreamKind, label: String) {
        self.id = streamId; self.kind = kind; self.label = label
    }
}

/// Receive-side state the local listener chose for a specific remote stream — mirrors
/// `vc_remote_stream_state`. All LOCAL (no protocol traffic) — docs/voice.md §10.
/// Defaults (if `setRemoteStream` was never called): gain 1.0, unmuted, NR off.
public struct RemoteStreamState: Sendable, Equatable {
    public let gain: Float      // 0.0–… ; default 1.0
    public let muted: Bool
    public let noiseReduction: Bool

    public init(gain: Float, muted: Bool, noiseReduction: Bool) {
        self.gain = gain; self.muted = muted; self.noiseReduction = noiseReduction
    }
}

/// Audio device — mirrors `vc_device`. `id` is an opaque, internally-encoded handle
/// (currently hex-encoded `ma_device_id`) — always round-trip an id from `listDevices`;
/// never construct one by hand (docs/architecture.md §4).
public struct Device: Sendable, Equatable, Identifiable {
    public let id: String
    public let name: String
    public let isDefault: Bool

    public init(id: String, name: String, isDefault: Bool) {
        self.id = id; self.name = name; self.isDefault = isDefault
    }
}

/// iOS audio input port — derived from `AVAudioSession.availableInputs`. Unlike the
/// miniaudio-based `Device` (which returns ~2 entries on iOS), this exposes the real
/// AVAudioSession input ports (builtInMic, bluetoothHFP, headsetMic, usbAudio, airPlay)
/// with their data sources (orientation: front/back/top/bottom) and polar patterns
/// (omni/cardioid/subcardioid/bidirectional). Used by `IOSAudioRouter` + `SettingsView`.
public struct IOSAudioInputPort: Identifiable, Hashable {
    public let id: String           // port UID (stable across route changes)
    public let name: String         // human-readable port name
    public let portType: String     // AVAudioSession.Port raw value as string
    public let dataSources: [IOSAudioDataSource]?
    public let isSelected: Bool     // true if this is the current preferredInput

    public init(id: String, name: String, portType: String,
                dataSources: [IOSAudioDataSource]?, isSelected: Bool) {
        self.id = id; self.name = name; self.portType = portType
        self.dataSources = dataSources; self.isSelected = isSelected
    }
}

/// iOS audio data source — a sub-selection of an input port (e.g. built-in mic
/// orientation: front/back/top/bottom). May have polar pattern options.
public struct IOSAudioDataSource: Identifiable, Hashable {
    public let id: String               // dataSource UID
    public let name: String             // "Front", "Back", "Top", "Bottom"
    public let polarPatterns: [String]? // AVAudioSession.PolarPattern raw values
    public let isSelected: Bool         // true if this is the current preferredDataSource
    public let selectedPolarPattern: String?

    public init(id: String, name: String, polarPatterns: [String]?,
                isSelected: Bool, selectedPolarPattern: String?) {
        self.id = id; self.name = name; self.polarPatterns = polarPatterns
        self.isSelected = isSelected; self.selectedPolarPattern = selectedPolarPattern
    }
}

/// iOS audio output route — read-only display of `AVAudioSession.currentRoute.outputs`.
public struct IOSAudioOutputRoute: Identifiable, Hashable {
    public let id: String       // port UID
    public let name: String     // human-readable route name
    public let portType: String // AVAudioSession.Port raw value as string

    public init(id: String, name: String, portType: String) {
        self.id = id; self.name = name; self.portType = portType
    }
}

/// Effective Opus configuration — mirrors `vc_audio_config`.
public struct AudioConfig: Sendable, Equatable {
    public let codec: UInt32            // 0 = OPUS
    public let stereo: Bool             // mode: 0 = mono, 1 = stereo
    public let sampleRate: UInt32
    public let bitrateBps: UInt32
    public let frameMs: UInt32
    public let application: UInt32      // 0 = VOIP, 1 = AUDIO, 2 = LOWDELAY
    public let fec: Bool
    public let expectedPacketLoss: UInt32   // % 0..100
    public let dtx: Bool
    public let complexity: UInt32       // 0..10
    public let dred: Bool               // Deep REDundancy (Opus 1.6), off by default

    public init(codec: UInt32 = 0, stereo: Bool = false, sampleRate: UInt32 = 48000,
                bitrateBps: UInt32 = 64000, frameMs: UInt32 = 20, application: UInt32 = 0,
                fec: Bool = true, expectedPacketLoss: UInt32 = 5, dtx: Bool = false,
                complexity: UInt32 = 10, dred: Bool = false) {
        self.codec = codec; self.stereo = stereo; self.sampleRate = sampleRate
        self.bitrateBps = bitrateBps; self.frameMs = frameMs; self.application = application
        self.fec = fec; self.expectedPacketLoss = expectedPacketLoss; self.dtx = dtx
        self.complexity = complexity; self.dred = dred
    }
}

/// Stream descriptor — mirrors `vc_stream_desc`. Used by `startStream(kind:deviceId:label:)`.
public struct StreamDescriptor: Sendable, Equatable {
    public let kind: VoiceCatStreamKind
    /// nil = default device for this kind.
    public let deviceId: String?
    public let label: String
    /// When true the caller feeds PCM via `feedPcm` (e.g. the iOS VPIO mic path) and the core
    /// skips opening a hardware capture device for this stream. Mirrors `vc_stream_desc.external_feed`.
    public let externalFeed: Bool

    public init(kind: VoiceCatStreamKind, deviceId: String? = nil, label: String,
                externalFeed: Bool = false) {
        self.kind = kind; self.deviceId = deviceId; self.label = label
        self.externalFeed = externalFeed
    }
}

/// Server identity info — parsed from a `.serverIdentity` event + `getServerIdentityDisplay()`.
/// The `tlsCertFingerprint` (SHA-256 hex of the TLS leaf cert) is the value the TOFU gate
/// actually pins on; `ed25519Fingerprint` is display-only (docs/security.md §1.1).
public struct ServerIdentity: Sendable, Equatable {
    public let tofuStatus: VoiceCatTofuStatus
    /// SHA-256 hex of the TLS leaf certificate — the pinned value. No separators (64 chars).
    public let tlsCertFingerprint: String
    /// Ed25519 identity fingerprint from ServerHello, hex-formatted — display only.
    /// Empty if not yet available.
    public let ed25519Fingerprint: String

    public init(tofuStatus: VoiceCatTofuStatus, tlsCertFingerprint: String,
                ed25519Fingerprint: String) {
        self.tofuStatus = tofuStatus; self.tlsCertFingerprint = tlsCertFingerprint
        self.ed25519Fingerprint = ed25519Fingerprint
    }
}
