// SPDX-FileCopyrightText: 2026 Contributors to brew-proxy <https://codeberg.org/HastD/brew-proxy>
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use std::{
    io::{PipeReader, PipeWriter},
    os::fd::OwnedFd,
};

use pty_process::Pty;
use serde::{Deserialize, Serialize};
use zvariant::{OwnedFd as OwnedFdSerial, Type};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Type)]
enum IoHandlesKind {
    Pty,
    Stdio,
}

#[derive(Debug, Deserialize, Serialize, Type)]
pub struct IoHandlesSerial {
    kind: IoHandlesKind,
    fds: Vec<OwnedFdSerial>,
}

/// Type representing either a file descriptor for a PTY, or a triple of file descriptors for
/// standard input, standard output, and standard error.
///
/// `IoHandles` infallibly converts between `IoHandlesSerial`, which is less ergonomic but can be
/// serialized and sent over DBus.
#[derive(Debug)]
#[non_exhaustive]
pub enum IoHandles {
    Pty(OwnedFd),
    Stdio {
        stdin: OwnedFd,
        stdout: OwnedFd,
        stderr: OwnedFd,
    },
}

impl From<IoHandles> for IoHandlesSerial {
    fn from(value: IoHandles) -> Self {
        match value {
            IoHandles::Pty(pty_fd) => Self {
                kind: IoHandlesKind::Pty,
                fds: vec![pty_fd.into()],
            },
            IoHandles::Stdio {
                stdin,
                stdout,
                stderr,
            } => Self {
                kind: IoHandlesKind::Stdio,
                fds: vec![stdin.into(), stdout.into(), stderr.into()],
            },
        }
    }
}

impl From<IoHandlesSerial> for IoHandles {
    fn from(value: IoHandlesSerial) -> Self {
        match value.kind {
            IoHandlesKind::Pty => {
                let [pty_fd] = value
                    .fds
                    .try_into()
                    .expect("should have a single FD for the PTY");
                Self::Pty(pty_fd.into())
            }
            IoHandlesKind::Stdio => {
                let [stdin, stdout, stderr] = value
                    .fds
                    .try_into()
                    .expect("should have exactly 3 FDs for stdin, stdout, stderr");
                Self::Stdio {
                    stdin: stdin.into(),
                    stdout: stdout.into(),
                    stderr: stderr.into(),
                }
            }
        }
    }
}

impl IoHandles {
    /// Create an `IoHandles` instance from a handle to a PTY.
    #[must_use]
    pub fn from_pty(pty: Pty) -> Self {
        let pty_fd = OwnedFd::from(pty);
        Self::Pty(pty_fd)
    }

    /// Create an `IoHandles` instance from pipes for standard input, standard output, and standard
    /// error.
    #[must_use]
    pub fn from_stdio(stdin: PipeWriter, stdout: PipeReader, stderr: PipeReader) -> Self {
        Self::Stdio {
            stdin: stdin.into(),
            stdout: stdout.into(),
            stderr: stderr.into(),
        }
    }
}
