use snakecase::ascii::to_snakecase;
use std::{
    collections::{HashMap, HashSet},
    error::Error,
    fmt::{Display, Formatter, Write},
    process::{Command, Stdio},
};

use zbus::{
    names::BusName,
    zvariant::{ObjectPath, Signature},
};
use zbus_xml::{
    Arg, ArgDirection, Interface, Property,
    telepathy::{self, TypeDef},
};

#[deprecated(since = "5.4.0", note = "use `CodeGenerator::file_code` instead")]
pub fn write_interfaces(
    interfaces: &[Interface<'_>],
    standard_interfaces: &[Interface<'_>],
    service: Option<BusName<'_>>,
    path: Option<ObjectPath<'_>>,
    input_src: &str,
    cargo_bin_name: &str,
    cargo_bin_version: &str,
) -> Result<String, Box<dyn Error + 'static>> {
    let code = CodeGenerator::new()
        .with_service(service.as_ref())
        .with_path(path.as_ref())
        .with_format(true)
        .file_code(
            interfaces,
            standard_interfaces,
            input_src,
            cargo_bin_name,
            cargo_bin_version,
        )?;

    Ok(code)
}

/// Write a doc header, listing the included Interfaces and how the
/// code was generated.
fn write_doc_header<W: std::fmt::Write>(
    w: &mut W,
    interfaces: &[Interface<'_>],
    standard_interfaces: &[Interface<'_>],
    node_types: &[TypeDef],
    input_src: &str,
    cargo_bin_name: &str,
    cargo_bin_version: &str,
) -> std::fmt::Result {
    if let Some((first_iface, following_ifaces)) = interfaces.split_first() {
        if following_ifaces.is_empty() {
            writeln!(
                w,
                "//! # D-Bus interface proxy for: `{}`",
                first_iface.name()
            )?;
        } else {
            write!(
                w,
                "//! # D-Bus interface proxies for: `{}`",
                first_iface.name()
            )?;
            for iface in following_ifaces {
                write!(w, ", `{}`", iface.name())?;
            }
            writeln!(w)?;
        }
    }

    write!(
        w,
        "//!
         //! This code was generated by `{cargo_bin_name}` `{cargo_bin_version}` from D-Bus introspection data.
         //! Source: `{input_src}`.
         //!
         //! You may prefer to adapt it, instead of using it verbatim.
         //!
         //! More information can be found in the [Writing a client proxy] section of the zbus
         //! documentation.
         //!
        ",
    )?;

    if !standard_interfaces.is_empty() {
        write!(w,
            "//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the
             //! following zbus API can be used:
             //!
            ")?;
        for iface in standard_interfaces {
            let idx = iface.name().rfind('.').unwrap() + 1;
            let name = &iface.name()[idx..];
            writeln!(w, "//! * [`zbus::fdo::{name}Proxy`]")?;
        }
        write!(
            w,
            "//!
             //! Consequently `{cargo_bin_name}` did not generate code for the above interfaces.
            ",
        )?;
    }

    let emitted_types: Vec<_> = interfaces
        .iter()
        .flat_map(|interface| Types::new(interface, node_types).emit)
        .collect();
    if !emitted_types.is_empty() {
        let serde_repr = emitted_types.iter().any(
            |def| matches!(def, TypeDef::Enum(e) if matches!(enum_repr(e), Some(EnumRepr::Int(_)))),
        );
        let crates = if serde_repr {
            "`serde` and `serde_repr` crates"
        } else {
            "`serde` crate"
        };
        write!(
            w,
            "//!
             //! This file also contains Rust definitions for the Telepathy type extensions
             //! (`tp:struct`, `tp:enum`, …) found in the introspection data. They rely on the
             //! {crates}, which the crate this code is placed in needs to depend on.
            ",
        )?;
    }

    write!(
        w,
        "//!
        //! [Writing a client proxy]: https://z-galaxy.github.io/zbus/client.html
        //! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
        use zbus::proxy;
        "
    )?;

    Ok(())
}

#[deprecated(since = "5.4.0", note = "use `CodeGenerator` instead")]
pub struct GenTrait<'i> {
    pub interface: &'i Interface<'i>,
    pub service: Option<&'i BusName<'i>>,
    pub path: Option<&'i ObjectPath<'i>>,
    pub format: bool,
}

#[allow(deprecated)]
impl Display for GenTrait<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let code = CodeGenerator::new()
            .with_service(self.service)
            .with_path(self.path)
            .with_format(self.format)
            .interface_code(self.interface)?;

        write!(f, "{code}")
    }
}

/// Generates Rust code from D-Bus introspection data.
///
/// The generator is configured through the builder-style `with_*` methods; the code is then
/// produced by [`CodeGenerator::interface_code`] (a proxy trait for one interface, along with
/// Rust definitions for the [Telepathy type definitions] it carries or references) or
/// [`CodeGenerator::file_code`] (a complete source file).
///
/// [Telepathy type definitions]: zbus_xml::telepathy::TypeDef
#[derive(Debug, Default, Clone)]
pub struct CodeGenerator<'i> {
    node_types: &'i [TypeDef],
    service: Option<&'i BusName<'i>>,
    path: Option<&'i ObjectPath<'i>>,
    format: bool,
}

impl<'i> CodeGenerator<'i> {
    /// Create a code generator with the default configuration.
    ///
    /// No type definitions in scope, no default service or path, no formatting.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the Telepathy type definitions from the enclosing `<node>`.
    ///
    /// The ones an interface references get a Rust definition generated alongside the
    /// definitions from the interface itself.
    pub fn with_node_types(mut self, node_types: &'i [TypeDef]) -> Self {
        self.node_types = node_types;
        self
    }

    /// Set the well-known service name, for the proxies' `default_service`.
    pub fn with_service(mut self, service: Option<&'i BusName<'i>>) -> Self {
        self.service = service;
        self
    }

    /// Set the object path, for the proxies' `default_path`.
    pub fn with_path(mut self, path: Option<&'i ObjectPath<'i>>) -> Self {
        self.path = path;
        self
    }

    /// Set whether the generated code is passed through `rustfmt`.
    ///
    /// If `rustfmt` fails or is not available, the unformatted code is returned instead.
    pub fn with_format(mut self, format: bool) -> Self {
        self.format = format;
        self
    }

    /// The Telepathy type definitions from the enclosing `<node>`.
    pub fn node_types(&self) -> &'i [TypeDef] {
        self.node_types
    }

    /// The well-known service name, if any.
    pub fn service(&self) -> Option<&'i BusName<'i>> {
        self.service
    }

    /// The object path, if any.
    pub fn path(&self) -> Option<&'i ObjectPath<'i>> {
        self.path
    }

    /// Whether the generated code is passed through `rustfmt`.
    pub fn format(&self) -> bool {
        self.format
    }

    /// Generate the code for `interface`: a proxy trait, preceded by Rust definitions for the
    /// Telepathy type definitions it carries or references.
    pub fn interface_code(&self, interface: &Interface<'_>) -> Result<String, std::fmt::Error> {
        let mut code = String::new();
        self.write_interface(&mut code, interface, &mut HashSet::new())?;

        Ok(format_if(self.format, code))
    }

    /// Generate a complete source file: a doc header followed by the code for `interfaces`.
    ///
    /// `standard_interfaces` are only mentioned in the header, as zbus provides their
    /// implementation; `input_src`, `cargo_bin_name` and `cargo_bin_version` document where
    /// the code came from.
    pub fn file_code(
        &self,
        interfaces: &[Interface<'_>],
        standard_interfaces: &[Interface<'_>],
        input_src: &str,
        cargo_bin_name: &str,
        cargo_bin_version: &str,
    ) -> Result<String, std::fmt::Error> {
        let mut code = String::new();
        write_doc_header(
            &mut code,
            interfaces,
            standard_interfaces,
            self.node_types,
            input_src,
            cargo_bin_name,
            cargo_bin_version,
        )?;
        // Interfaces in one file may reference the same node-level type definitions; each
        // Rust definition is written only once.
        let mut emitted = HashSet::new();
        for interface in interfaces {
            self.write_interface(&mut code, interface, &mut emitted)?;
        }

        Ok(format_if(self.format, code))
    }

    fn write_interface<W: Write>(
        &self,
        w: &mut W,
        iface: &Interface<'_>,
        emitted: &mut HashSet<String>,
    ) -> std::fmt::Result {
        let idx = iface.name().rfind('.').unwrap() + 1;
        let name = &iface.name()[idx..];

        let types = Types::new(iface, self.node_types);
        write_type_defs(w, &types, emitted)?;

        if let Some(docstring) = iface.docstring() {
            write_doc_lines(w, docstring, "")?;
        }
        write!(w, "#[proxy(interface = \"{}\"", iface.name())?;
        if let Some(service) = self.service {
            write!(w, ", default_service = \"{service}\"")?;
        }
        if let Some(path) = self.path {
            write!(w, ", default_path = \"{path}\"")?;
        }
        if self.path.is_none() || self.service.is_none() {
            write!(w, ", assume_defaults = true")?;
        }
        writeln!(w, ")]")?;
        writeln!(w, "pub trait {name} {{")?;

        let mut methods = iface.methods().to_vec();
        methods.sort_by(|a, b| a.name().partial_cmp(&b.name()).unwrap());
        for m in &methods {
            let (inputs, output) = inputs_output_from_args(m.args(), &types);
            let name = to_identifier(&to_snakecase(m.name().as_str()));
            writeln!(w)?;
            writeln!(w, "    /// {} method", m.name())?;
            write_member_docs(w, m.docstring(), m.args())?;
            if pascal_case(&name) != m.name().as_str() {
                writeln!(w, "    #[zbus(name = \"{}\")]", m.name())?;
            }
            hide_clippy_lints(w, m, &types)?;
            writeln!(w, "    fn {name}({inputs}){output};")?;
        }

        let mut signals = iface.signals().to_vec();
        signals.sort_by(|a, b| a.name().partial_cmp(&b.name()).unwrap());
        for signal in &signals {
            let args = parse_signal_args(signal.args(), &types);
            let name = to_identifier(&to_snakecase(signal.name().as_str()));
            writeln!(w)?;
            writeln!(w, "    /// {} signal", signal.name())?;
            write_member_docs(w, signal.docstring(), signal.args())?;
            if pascal_case(&name) != signal.name().as_str() {
                writeln!(w, "    #[zbus(signal, name = \"{}\")]", signal.name())?;
            } else {
                writeln!(w, "    #[zbus(signal)]")?;
            }
            writeln!(w, "    fn {name}({args}) -> zbus::Result<()>;",)?;
        }

        let mut props = iface.properties().to_vec();
        props.sort_by(|a, b| a.name().partial_cmp(&b.name()).unwrap());
        for p in props {
            let name = to_identifier(&to_snakecase(p.name().as_str()));
            let fn_attribute = if pascal_case(&name) != p.name().as_str() {
                format!("    #[zbus(property, name = \"{}\")]", p.name())
            } else {
                "    #[zbus(property)]".to_string()
            };

            writeln!(w)?;
            writeln!(w, "    /// {} property", p.name())?;
            write_member_docs(w, p.docstring(), &[])?;
            // Named types satisfy both directions of the property value conversions, owned.
            let named = types.resolve(p.tp_type(), p.ty(), false);
            if p.access().read() {
                writeln!(w, "{fn_attribute}")?;
                let output = match &named {
                    Some(named) => named.clone(),
                    None => {
                        hide_clippy_type_complexity_lint(w, p.ty())?;
                        to_rust_type(p.ty(), false, false)
                    }
                };
                writeln!(w, "    fn {name}(&self) -> zbus::Result<{output}>;",)?;
            }

            if p.access().write() {
                writeln!(w, "{fn_attribute}")?;
                let input = named.unwrap_or_else(|| to_property_setter_type(p.ty()));
                writeln!(
                    w,
                    "    fn set_{name}(&self, value: {input}) -> zbus::Result<()>;",
                )?;
            }
        }
        writeln!(w, "}}")
    }
}

/// Write a docstring (from the Telepathy introspection extensions) as rustdoc comment lines.
///
/// The content is typically HTML, which rustdoc passes through; only the indentation is
/// rewritten, as the original one is meaningless after the lines are prefixed with `///`.
fn write_doc_lines<W: Write>(w: &mut W, docstring: &str, indent: &str) -> std::fmt::Result {
    for line in doc_markdown(docstring).lines() {
        if line.is_empty() {
            writeln!(w, "{indent}///")?;
        } else {
            writeln!(w, "{indent}/// {line}")?;
        }
    }
    Ok(())
}

/// Convert a Telepathy docstring — an HTML fragment — into Markdown for a rustdoc comment.
///
/// The docstrings embedded by the Telepathy extensions are HTML (`<p>`, `<em>`, and their own
/// `<tp:rationale>`, …). Emitting them verbatim would leak the tags into the generated docs, so
/// they are converted: block-level tags become paragraph breaks (a blank line), `<em>`,
/// `<strong>` and `<code>` become their Markdown equivalents, any other tag is stripped, entity
/// references are resolved and insignificant HTML whitespace is collapsed. The result is a
/// sequence of single-line paragraphs separated by blank lines — so a bare carriage return
/// cannot survive into a doc comment either.
fn doc_markdown(docstring: &str) -> String {
    fn flush(paragraphs: &mut Vec<String>, current: &mut String) {
        let paragraph = current.split_whitespace().collect::<Vec<_>>().join(" ");
        current.clear();
        if !paragraph.is_empty() {
            paragraphs.push(paragraph);
        }
    }

    let mut paragraphs = Vec::new();
    let mut current = String::new();
    let mut rest = docstring;
    while let Some(open) = rest.find('<') {
        push_text(&mut current, &rest[..open]);
        rest = &rest[open..];

        // Comments, CDATA sections and processing instructions.
        if let Some(after) = rest.strip_prefix("<!--") {
            rest = after.split_once("-->").map_or("", |(_, after)| after);
            continue;
        }
        if let Some(after) = rest.strip_prefix("<![CDATA[") {
            let (cdata, after) = after.split_once("]]>").unwrap_or((after, ""));
            current.push_str(cdata); // CDATA content is literal text.
            rest = after;
            continue;
        }
        if rest.starts_with("<?") {
            rest = rest.split_once("?>").map_or("", |(_, after)| after);
            continue;
        }

        let Some(close) = rest.find('>') else {
            push_text(&mut current, rest);
            return finish(paragraphs, current);
        };
        let tag = &rest[1..close];
        rest = &rest[close + 1..];

        let name = tag
            .trim_start_matches('/')
            .split([' ', '\t', '\n', '\r', '/'])
            .next()
            .unwrap_or("");
        // Match on the local name, so a namespace prefix (e. g. `tp:`) doesn't matter.
        match name
            .rsplit(':')
            .next()
            .unwrap_or(name)
            .to_ascii_lowercase()
            .as_str()
        {
            "strong" | "b" => current.push_str("**"),
            "em" | "i" => current.push('*'),
            "code" | "tt" => current.push('`'),
            "p" | "br" | "div" | "blockquote" | "ul" | "ol" | "li" | "dl" | "dt" | "dd" | "pre"
            | "rationale" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => {
                flush(&mut paragraphs, &mut current)
            }
            _ => {} // Strip any other tag.
        }
    }
    push_text(&mut current, rest);
    finish(paragraphs, current)
}

/// Join a docstring's paragraphs, flushing any final one.
fn finish(mut paragraphs: Vec<String>, current: String) -> String {
    let last = current.split_whitespace().collect::<Vec<_>>().join(" ");
    if !last.is_empty() {
        paragraphs.push(last);
    }
    paragraphs.join("\n\n")
}

/// Append `text` to `out`, resolving XML entity references (`&amp;`, `&#65;`, …).
fn push_text(out: &mut String, text: &str) {
    let mut rest = text;
    while let Some(amp) = rest.find('&') {
        out.push_str(&rest[..amp]);
        rest = &rest[amp..];
        match rest[1..]
            .split_once(';')
            .and_then(|(entity, after)| decode_entity(entity).map(|c| (c, after)))
        {
            Some((c, after)) => {
                out.push(c);
                rest = after;
            }
            // Not a recognized reference: keep the `&` literal.
            None => {
                out.push('&');
                rest = &rest[1..];
            }
        }
    }
    out.push_str(rest);
}

/// Resolve an XML entity reference (the part between `&` and `;`).
fn decode_entity(entity: &str) -> Option<char> {
    match entity {
        "amp" => return Some('&'),
        "lt" => return Some('<'),
        "gt" => return Some('>'),
        "quot" => return Some('"'),
        "apos" => return Some('\''),
        _ => {}
    }
    let dec_or_hex = entity.strip_prefix('#')?;
    let code = match dec_or_hex.strip_prefix(['x', 'X']) {
        Some(hex) => u32::from_str_radix(hex, 16).ok()?,
        None => dec_or_hex.parse().ok()?,
    };
    char::from_u32(code)
}

/// Write the docstrings of an interface member and of its args, if any, as rustdoc comments.
fn write_member_docs<W: Write>(
    w: &mut W,
    docstring: Option<&str>,
    args: &[Arg],
) -> std::fmt::Result {
    if let Some(docstring) = docstring {
        writeln!(w, "    ///")?;
        write_doc_lines(w, docstring, "    ")?;
    }

    let mut wrote_header = false;
    for arg in args {
        let (Some(name), Some(docstring)) = (arg.name(), arg.docstring()) else {
            continue;
        };
        if !wrote_header {
            writeln!(w, "    ///")?;
            writeln!(w, "    /// # Arguments")?;
            writeln!(w, "    ///")?;
            wrote_header = true;
        }
        let markdown = doc_markdown(docstring);
        let mut lines = markdown.lines();
        writeln!(
            w,
            "    /// * `{}` - {}",
            to_identifier(name),
            lines.next().unwrap_or_default()
        )?;
        // Indent continuation lines so they stay part of the list item.
        for line in lines {
            if line.is_empty() {
                writeln!(w, "    ///")?;
            } else {
                writeln!(w, "    ///   {line}")?;
            }
        }
    }

    Ok(())
}

/// The Telepathy type definitions in scope for an interface.
struct Types<'i> {
    /// The definitions whose Rust definition is generated, by their Telepathy name.
    ///
    /// Only these are used when resolving `tp:type` references; anything else falls back to
    /// the structural Rust type.
    generated: HashMap<&'i str, &'i TypeDef>,
    /// The same definitions in emission order (the interface's own first, then the
    /// referenced node-level ones).
    emit: Vec<&'i TypeDef>,
}

impl<'i> Types<'i> {
    fn new(interface: &'i Interface<'i>, node_types: &'i [TypeDef]) -> Self {
        // All definitions in scope, the interface's own shadowing same-named node-level ones.
        let mut by_name = HashMap::new();
        for def in node_types.iter().chain(interface.telepathy_types()) {
            by_name.insert(def.name(), def);
        }

        // The names the interface references through `tp:type`, transitively through other
        // definitions' members.
        let mut referenced = HashSet::new();
        let mut pending: Vec<&str> = interface
            .methods()
            .iter()
            .flat_map(|m| m.args())
            .chain(interface.signals().iter().flat_map(|s| s.args()))
            .filter_map(Arg::tp_type)
            .chain(interface.properties().iter().filter_map(Property::tp_type))
            .collect();
        for def in interface.telepathy_types() {
            member_references(def, &mut pending);
        }
        while let Some(reference) = pending.pop() {
            let (name, _) = strip_arrays(reference);
            if !referenced.insert(name) {
                continue;
            }
            if let Some(def) = by_name.get(name) {
                member_references(def, &mut pending);
            }
        }

        // Each Rust type name is only generated once. Names that clash with the items the
        // `proxy` macro will emit for this interface are off-limits from the start; the
        // remaining conflicts (distinct Telepathy names mapping to one Rust name) are won by
        // the first taker, in priority order: the interface's own definitions, then the
        // referenced node-level ones. Losers are not generated, so references to them fall
        // back to the structural type.
        let idx = interface.name().rfind('.').unwrap() + 1;
        let trait_name = &interface.name()[idx..];
        let mut taken: HashSet<String> = ["Proxy", "ProxyBlocking"]
            .iter()
            .map(|suffix| format!("{trait_name}{suffix}"))
            .chain(interface.signals().iter().flat_map(|signal| {
                let signal = pascal_case(signal.name().as_str());
                [
                    signal.clone(),
                    format!("{signal}Args"),
                    format!("{signal}Stream"),
                ]
            }))
            .collect();

        let mut generated = HashMap::new();
        let mut emit = Vec::new();
        let node_defs = node_types
            .iter()
            .filter(|def| referenced.contains(def.name()));
        for def in interface.telepathy_types().iter().chain(node_defs) {
            if generatable(def)
                && !generated.contains_key(def.name())
                && taken.insert(type_name(def.name()))
            {
                generated.insert(def.name(), def);
                emit.push(def);
            }
        }

        Self { generated, emit }
    }

    /// Resolve a `tp:type` reference against `signature` into a generated Rust type.
    ///
    /// Returns `None` — leaving the caller to fall back to the structural Rust type — when
    /// there is no reference, it doesn't name a generated definition, or `signature` doesn't
    /// match the definition. `input` selects the borrowed form used for method inputs.
    fn resolve(
        &self,
        reference: Option<&str>,
        signature: &Signature,
        input: bool,
    ) -> Option<String> {
        let (name, arrays) = strip_arrays(reference?);
        let def = *self.generated.get(name)?;

        let mut signature = signature;
        for _ in 0..arrays {
            match signature {
                Signature::Array(child) => signature = child.signature(),
                _ => return None,
            }
        }
        if *signature != def.signature() {
            return None;
        }

        let base = type_name(def.name());
        if !input {
            return Some((0..arrays).fold(base, |ty, _| format!("Vec<{ty}>")));
        }
        Some(if arrays > 0 {
            let inner = (0..arrays - 1).fold(base, |ty, _| format!("Vec<{ty}>"));
            format!("&[{inner}]")
        } else if passed_by_value(def) {
            base
        } else {
            format!("&{base}")
        })
    }

    /// Like [`Types::resolve`], but only for names given to plain D-Bus types.
    ///
    /// Dictionary keys must remain basic types on the Rust side too — a struct or enum key
    /// would not satisfy the dictionary conversions — so only [`telepathy::SimpleType`]
    /// aliases are resolved for them.
    fn resolve_simple(&self, reference: Option<&str>, signature: &Signature) -> Option<String> {
        let (name, _) = strip_arrays(reference?);
        match self.generated.get(name) {
            Some(TypeDef::SimpleType(_)) => self.resolve(reference, signature, false),
            _ => None,
        }
    }
}

/// Push the `tp:type` references of `def`'s members, if any, onto `pending`.
fn member_references<'i>(def: &'i TypeDef, pending: &mut Vec<&'i str>) {
    match def {
        TypeDef::Struct(s) => pending.extend(s.members().iter().filter_map(|m| m.tp_type())),
        TypeDef::Mapping(m) => {
            pending.extend([m.key(), m.value()].into_iter().filter_map(|m| m.tp_type()))
        }
        _ => (),
    }
}

/// Split a `tp:type` reference into the type name and its number of `[]` suffixes.
fn strip_arrays(reference: &str) -> (&str, usize) {
    let mut name = reference;
    let mut arrays = 0;
    while let Some(stripped) = name.strip_suffix("[]") {
        name = stripped;
        arrays += 1;
    }
    (name, arrays)
}

/// Whether a Rust definition can be generated for `def`.
fn generatable(def: &TypeDef) -> bool {
    if !is_valid_ident(&type_name(def.name())) {
        return false;
    }
    match def {
        TypeDef::SimpleType(_) | TypeDef::Mapping(_) => true,
        TypeDef::Enum(e) => {
            let Some(repr) = enum_repr(e) else {
                return false;
            };
            // Variant names must be distinct identifiers (e. g. `Foo_Bar` and `FooBar` map
            // to the same name), and the values must be distinct too: on the Rust side
            // duplicate discriminants don't compile, and for string enums a duplicate makes
            // the wire mapping ambiguous.
            let mut names = HashSet::new();
            let mut values = HashSet::new();
            !e.values().is_empty()
                && e.values().iter().all(|v| {
                    let name = variant_name(v);
                    is_valid_ident(&name)
                        && names.insert(name)
                        && match repr {
                            EnumRepr::Int(int) => match enum_value(int, v.value()) {
                                Some(value) => values.insert(value.to_string()),
                                None => false,
                            },
                            EnumRepr::Str => values.insert(v.value().to_owned()),
                        }
                })
        }
        TypeDef::Struct(s) => {
            let mut names = HashSet::new();
            s.members().iter().all(|m| {
                let name = field_name(m.name());
                is_valid_ident(&name) && names.insert(name)
            })
        }
    }
}

/// Parse the value of a numeric enum, checking that it fits the representation.
fn enum_value(repr: &str, value: &str) -> Option<i128> {
    let (min, max) = match repr {
        "u8" => (0, u8::MAX as i128),
        "i16" => (i16::MIN as i128, i16::MAX as i128),
        "u16" => (0, u16::MAX as i128),
        "i32" => (i32::MIN as i128, i32::MAX as i128),
        "u32" => (0, u32::MAX as i128),
        "i64" => (i64::MIN as i128, i64::MAX as i128),
        "u64" => (0, u64::MAX as i128),
        _ => return None,
    };

    value
        .parse()
        .ok()
        .filter(|value| (min..=max).contains(value))
}

/// How a generated enum is represented on the wire.
enum EnumRepr {
    /// As an integer type, e. g. `u32`.
    Int(&'static str),
    /// As a string.
    Str,
}

fn enum_repr(e: &telepathy::Enum) -> Option<EnumRepr> {
    match e.ty().inner() {
        Signature::U8 => Some(EnumRepr::Int("u8")),
        Signature::I16 => Some(EnumRepr::Int("i16")),
        Signature::U16 => Some(EnumRepr::Int("u16")),
        Signature::I32 => Some(EnumRepr::Int("i32")),
        Signature::U32 => Some(EnumRepr::Int("u32")),
        Signature::I64 => Some(EnumRepr::Int("i64")),
        Signature::U64 => Some(EnumRepr::Int("u64")),
        Signature::Str => Some(EnumRepr::Str),
        _ => None,
    }
}

/// Whether values of the generated type are cheap enough to pass inputs by value.
fn passed_by_value(def: &TypeDef) -> bool {
    match def {
        // Generated enums are `Copy`.
        TypeDef::Enum(_) => true,
        TypeDef::SimpleType(t) => matches!(
            t.ty().inner(),
            Signature::U8
                | Signature::Bool
                | Signature::I16
                | Signature::U16
                | Signature::I32
                | Signature::U32
                | Signature::I64
                | Signature::U64
                | Signature::F64
        ),
        _ => false,
    }
}

/// The Rust name for a Telepathy type, e. g. `Playlist_Id` -> `PlaylistId`.
fn type_name(name: &str) -> String {
    to_identifier(&pascal_case(name))
}

/// The Rust name for an enum variant, from the value's `suffix`.
fn variant_name(value: &telepathy::EnumValue) -> String {
    to_identifier(&pascal_case(value.suffix()))
}

/// The Rust name for a struct field, from the member's `name`.
fn field_name(name: &str) -> String {
    to_identifier(&to_snakecase(name))
}

fn is_valid_ident(s: &str) -> bool {
    let mut chars = s.chars();
    chars
        .next()
        .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
        && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}

/// Format `code` through rustfmt if `format` is set, falling back to it unformatted.
fn format_if(format: bool, code: String) -> String {
    if !format {
        return code;
    }
    match format_generated_code(&code) {
        Ok(formatted) => formatted,
        Err(e) => {
            eprintln!("Failed to format generated code: {e}");
            code
        }
    }
}

/// Write the Rust definitions for the type definitions in scope.
///
/// Definitions whose Rust name is already in `emitted` — node-level definitions shared with
/// an earlier interface in the same file — are skipped.
fn write_type_defs<W: Write>(
    w: &mut W,
    types: &Types<'_>,
    emitted: &mut HashSet<String>,
) -> std::fmt::Result {
    for def in &types.emit {
        if !emitted.insert(type_name(def.name())) {
            continue;
        }
        match def {
            TypeDef::SimpleType(t) => write_simple_type(w, t)?,
            TypeDef::Enum(e) => write_enum(w, e)?,
            TypeDef::Struct(s) => write_struct(w, s, types)?,
            TypeDef::Mapping(m) => write_mapping(w, m, types)?,
        }
        writeln!(w)?;
    }
    Ok(())
}

fn write_simple_type<W: Write>(w: &mut W, t: &telepathy::SimpleType) -> std::fmt::Result {
    if let Some(docstring) = t.docstring() {
        write_doc_lines(w, docstring, "")?;
    }
    writeln!(
        w,
        "pub type {} = {};",
        type_name(t.name()),
        to_rust_type(t.ty(), false, false)
    )
}

fn write_enum<W: Write>(w: &mut W, e: &telepathy::Enum) -> std::fmt::Result {
    if let Some(docstring) = e.docstring() {
        write_doc_lines(w, docstring, "")?;
    }
    let repr = enum_repr(e).expect("only generatable enums are emitted");
    match repr {
        EnumRepr::Int(int) => {
            writeln!(
                w,
                "#[derive(Debug, Clone, Copy, PartialEq, Eq, \
                 serde_repr::Deserialize_repr, serde_repr::Serialize_repr, \
                 zbus::zvariant::Type, zbus::zvariant::Value, zbus::zvariant::OwnedValue)]"
            )?;
            writeln!(w, "#[zvariant(crate = \"zbus::zvariant\")]")?;
            writeln!(w, "#[repr({int})]")?;
            writeln!(w, "pub enum {} {{", type_name(e.name()))?;
            for value in e.values() {
                if let Some(docstring) = value.docstring() {
                    write_doc_lines(w, docstring, "    ")?;
                }
                // Writing the parsed value normalizes forms Rust does not accept verbatim,
                // like a leading `+`.
                let discriminant =
                    enum_value(int, value.value()).expect("only generatable enums are emitted");
                writeln!(w, "    {} = {discriminant},", variant_name(value))?;
            }
        }
        EnumRepr::Str => {
            writeln!(
                w,
                "#[derive(Debug, Clone, Copy, PartialEq, Eq, \
                 serde::Serialize, serde::Deserialize, \
                 zbus::zvariant::Type, zbus::zvariant::Value, zbus::zvariant::OwnedValue)]"
            )?;
            writeln!(
                w,
                "#[zvariant(signature = \"s\", crate = \"zbus::zvariant\")]"
            )?;
            writeln!(w, "pub enum {} {{", type_name(e.name()))?;
            for value in e.values() {
                if let Some(docstring) = value.docstring() {
                    write_doc_lines(w, docstring, "    ")?;
                }
                let variant = variant_name(value);
                if variant != value.value() {
                    writeln!(w, "    #[serde(rename = {0:?})]", value.value())?;
                    writeln!(w, "    #[zvariant(rename = {0:?})]", value.value())?;
                }
                writeln!(w, "    {variant},")?;
            }
        }
    }
    writeln!(w, "}}")
}

fn write_struct<W: Write>(w: &mut W, s: &telepathy::Struct, types: &Types<'_>) -> std::fmt::Result {
    if let Some(docstring) = s.docstring() {
        write_doc_lines(w, docstring, "")?;
    }
    writeln!(
        w,
        "#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, \
         zbus::zvariant::Type, zbus::zvariant::Value, zbus::zvariant::OwnedValue)]"
    )?;
    writeln!(w, "#[zvariant(crate = \"zbus::zvariant\")]")?;
    writeln!(w, "pub struct {} {{", type_name(s.name()))?;
    for member in s.members() {
        if let Some(docstring) = member.docstring() {
            write_doc_lines(w, docstring, "    ")?;
        }
        let ty = types
            .resolve(member.tp_type(), member.ty(), false)
            .unwrap_or_else(|| to_rust_type(member.ty(), false, false));
        writeln!(w, "    pub {}: {},", field_name(member.name()), ty)?;
    }
    writeln!(w, "}}")
}

fn write_mapping<W: Write>(
    w: &mut W,
    m: &telepathy::Mapping,
    types: &Types<'_>,
) -> std::fmt::Result {
    if let Some(docstring) = m.docstring() {
        write_doc_lines(w, docstring, "")?;
    }
    let key = types
        .resolve_simple(m.key().tp_type(), m.key().ty())
        .unwrap_or_else(|| to_rust_type(m.key().ty(), false, false));
    let value = types
        .resolve(m.value().tp_type(), m.value().ty(), false)
        .unwrap_or_else(|| to_rust_type(m.value().ty(), false, false));
    writeln!(
        w,
        "pub type {} = std::collections::HashMap<{key}, {value}>;",
        type_name(m.name()),
    )
}

fn hide_clippy_lints<W: Write>(
    write: &mut W,
    method: &zbus_xml::Method<'_>,
    types: &Types<'_>,
) -> std::fmt::Result {
    // check for <https://rust-lang.github.io/rust-clippy/master/index.html#/too_many_arguments>
    // triggers when a functions has at least 7 paramters
    if method.args().len() >= 7 {
        writeln!(write, "    #[allow(clippy::too_many_arguments)]")?;
    }

    // check for <https://rust-lang.github.io/rust-clippy/master/index.html#/type_complexity>
    for arg in method.args() {
        // A named type keeps the written-out type simple, no matter the signature.
        if types.resolve(arg.tp_type(), arg.ty(), false).is_some() {
            continue;
        }
        let signature = arg.ty();
        hide_clippy_type_complexity_lint(write, signature)?;
    }

    Ok(())
}

fn hide_clippy_type_complexity_lint<W: Write>(
    write: &mut W,
    signature: &Signature,
) -> std::fmt::Result {
    let complexity = estimate_type_complexity(signature);
    if complexity >= 1700 {
        writeln!(write, "    #[allow(clippy::type_complexity)]")?;
    }
    Ok(())
}

fn inputs_output_from_args(args: &[Arg], types: &Types<'_>) -> (String, String) {
    let mut inputs = vec!["&self".to_string()];
    let mut output: Vec<OutputArg> = vec![];
    let mut n = 0;
    let mut gen_name = || {
        n += 1;
        format!("arg_{n}")
    };

    for a in args {
        match a.direction() {
            None | Some(ArgDirection::In) => {
                let ty = types
                    .resolve(a.tp_type(), a.ty(), true)
                    .unwrap_or_else(|| to_rust_type(a.ty(), true, true));
                let arg = if let Some(name) = a.name() {
                    to_identifier(name)
                } else {
                    gen_name()
                };
                inputs.push(format!("{arg}: {ty}"));
            }
            Some(ArgDirection::Out) => {
                let ty = types
                    .resolve(a.tp_type(), a.ty(), false)
                    .unwrap_or_else(|| to_rust_type(a.ty(), false, false));
                let is_struct = matches!(a.ty().inner(), Signature::Structure(_));
                output.push(OutputArg { ty, is_struct });
            }
        }
    }

    let output_str = match &output[..] {
        [] => "()".to_string(),
        [
            OutputArg {
                ty,
                is_struct: true,
            },
        ] => {
            // If there's a single output argument and it is a struct type, we need to wrap it
            // in a tuple to distinguish it from multiple return values
            format!("({ty},)")
        }
        [
            OutputArg {
                ty,
                is_struct: false,
            },
        ] => ty.clone(),
        multiple => {
            let types = multiple
                .iter()
                .map(|arg| arg.ty.as_str())
                .collect::<Vec<_>>();
            format!("({})", types.join(", "))
        }
    };

    (inputs.join(", "), format!(" -> zbus::Result<{output_str}>"))
}

#[derive(Debug)]
struct OutputArg {
    ty: String,
    is_struct: bool,
}

fn parse_signal_args(args: &[Arg], types: &Types<'_>) -> String {
    let mut inputs = vec!["&self".to_string()];
    let mut n = 0;
    let mut gen_name = || {
        n += 1;
        format!("arg_{n}")
    };

    for a in args {
        // Signal args are deserialized, so a named type is used in its owned form.
        let ty = types
            .resolve(a.tp_type(), a.ty(), false)
            .unwrap_or_else(|| to_rust_type(a.ty(), true, false));
        let arg = if let Some(name) = a.name() {
            to_identifier(name)
        } else {
            gen_name()
        };
        inputs.push(format!("{arg}: {ty}"));
    }

    inputs.join(", ")
}

fn to_rust_type(ty: &Signature, input: bool, as_ref: bool) -> String {
    // can't haz recursive closure, yet
    fn signature_to_rust_type(signature: &Signature, input: bool, as_ref: bool) -> String {
        match signature {
            Signature::Unit => "".into(),
            Signature::U8 => "u8".into(),
            Signature::Bool => "bool".into(),
            Signature::I16 => "i16".into(),
            Signature::U16 => "u16".into(),
            Signature::I32 => "i32".into(),
            Signature::U32 => "u32".into(),
            Signature::I64 => "i64".into(),
            Signature::U64 => "u64".into(),
            Signature::F64 => "f64".into(),
            #[cfg(unix)]
            Signature::Fd if input => "zbus::zvariant::Fd<'_>".into(),
            #[cfg(unix)]
            Signature::Fd => "zbus::zvariant::OwnedFd".into(),
            Signature::Str if input || as_ref => "&str".into(),
            Signature::Str => "String".into(),
            Signature::ObjectPath if input => {
                if as_ref {
                    "&zbus::zvariant::ObjectPath<'_>".into()
                } else {
                    "zbus::zvariant::ObjectPath<'_>".into()
                }
            }
            Signature::ObjectPath => "zbus::zvariant::OwnedObjectPath".into(),
            // `zvariant::Signature` has been lifetime-less (with no `Owned` counterpart)
            // since zvariant 5.
            Signature::Signature if input && as_ref => "&zbus::zvariant::Signature".into(),
            Signature::Signature => "zbus::zvariant::Signature".into(),
            Signature::Variant if input => {
                if as_ref {
                    "&zbus::zvariant::Value<'_>".into()
                } else {
                    "zbus::zvariant::Value<'_>".into()
                }
            }
            Signature::Variant => "zbus::zvariant::OwnedValue".into(),
            Signature::Array(child) => {
                let child_ty = signature_to_rust_type(child, input, as_ref);
                if input && as_ref {
                    format!("&[{child_ty}]")
                } else {
                    format!("Vec<{child_ty}>")
                }
            }
            Signature::Dict { key, value } => {
                let key_ty = signature_to_rust_type(key, input, as_ref);
                let value_ty = signature_to_rust_type(value, input, as_ref);

                format!("std::collections::HashMap<{key_ty}, {value_ty}>")
            }
            Signature::Structure(fields) => {
                let fields = fields
                    .iter()
                    .map(|f| signature_to_rust_type(f, input, as_ref))
                    .collect::<Vec<_>>();

                if fields.len() > 1 {
                    format!("{}({})", if as_ref { "&" } else { "" }, fields.join(", "))
                } else {
                    format!("{}({},)", if as_ref { "&" } else { "" }, fields[0])
                }
            }
            #[allow(unreachable_patterns)]
            _ => unreachable!("Unsupported signature: {}", signature),
        }
    }

    signature_to_rust_type(ty, input, as_ref)
}

// Like `to_rust_type` for an input parameter, but tailored to property setters.
//
// `Proxy::set_property` requires the value to satisfy `T: Into<Value<'t>>`. For most
// types, the `&T` form already implements this (via `From<&T> for Value`). For `Variant`
// and `Structure`, the `&T` form does not, so the owned form is emitted instead, also
// when these appear nested inside an `Array` or `Dict`.
fn to_property_setter_type(ty: &Signature) -> String {
    fn inner(signature: &Signature) -> String {
        match signature {
            Signature::Variant => "zbus::zvariant::Value<'_>".into(),
            Signature::Structure(fields) => {
                let fields = fields.iter().map(inner).collect::<Vec<_>>();
                if fields.len() > 1 {
                    format!("({})", fields.join(", "))
                } else {
                    format!("({},)", fields[0])
                }
            }
            Signature::Array(child) => format!("&[{}]", inner(child)),
            Signature::Dict { key, value } => {
                format!(
                    "std::collections::HashMap<{}, {}>",
                    inner(key),
                    inner(value),
                )
            }
            _ => to_rust_type(signature, true, true),
        }
    }
    inner(ty)
}

static KWORDS: &[&str] = &[
    "Self", "abstract", "as", "async", "await", "become", "box", "break", "const", "continue",
    "crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl",
    "in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
    "return", "self", "static", "struct", "super", "trait", "true", "try", "type", "typeof",
    "union", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
];

fn to_identifier(id: &str) -> String {
    if KWORDS.contains(&id) {
        format!("{id}_")
    } else {
        id.replace('-', "_")
    }
}

// This function is the same as zbus_macros::utils::pascal_case
pub fn pascal_case(s: &str) -> String {
    let mut pascal = String::new();
    let mut capitalize = true;
    for ch in s.chars() {
        if ch == '_' {
            capitalize = true;
        } else if capitalize {
            pascal.push(ch.to_ascii_uppercase());
            capitalize = false;
        } else {
            pascal.push(ch);
        }
    }
    pascal
}

fn estimate_type_complexity(signature: &Signature) -> u32 {
    let mut score = 0;

    match signature {
        Signature::Unit => (),
        Signature::U8
        | Signature::Bool
        | Signature::I16
        | Signature::U16
        | Signature::I32
        | Signature::U32
        | Signature::I64
        | Signature::U64
        | Signature::F64
        | Signature::Str => score += 1,
        #[cfg(unix)]
        Signature::Fd => score += 10,
        Signature::ObjectPath | Signature::Signature | Signature::Variant => score += 10,
        Signature::Array(child) => score += 5 * estimate_type_complexity(child),
        Signature::Dict { key, value } => {
            score *= 10 + 50;
            score += 5 * estimate_type_complexity(key);
            score += 5 * estimate_type_complexity(value);
        }
        Signature::Structure(fields) => {
            score += 50;
            for field in fields.iter() {
                score += 5 * estimate_type_complexity(field);
            }
        }
        #[allow(unreachable_patterns)]
        _ => unreachable!("Unsupported signature: {}", signature),
    }

    score
}

fn format_generated_code(generated_code: &str) -> std::io::Result<String> {
    use std::io::{Read, Write};

    let mut process = Command::new("rustfmt")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        // rustfmt may post warnings about features not being enabled on stable rust
        // these can be distracting and are irrevelant to the user, so we hide them
        .stderr(Stdio::null())
        .spawn()?;
    let rustfmt_stdin = process.stdin.as_mut().unwrap();
    let mut rustfmt_stdout = process.stdout.take().unwrap();
    writeln!(rustfmt_stdin)?;
    rustfmt_stdin.write_all(generated_code.as_bytes())?;

    let exit_status = process.wait()?;
    if !exit_status.success() {
        eprintln!("`rustfmt` did not exit successfully. Continuing with unformatted code.");
        return Ok(generated_code.to_string());
    }

    let mut formatted = String::new();
    rustfmt_stdout.read_to_string(&mut formatted)?;

    Ok(formatted)
}
