use camino::Utf8PathBuf;
use core::fmt;
use core::fmt::Formatter;
use core::marker::PhantomData;
use serde::Deserialize;
use serde::Deserializer;

#[derive(Clone, Debug)]
pub struct Config {
    pub output_path: Utf8PathBuf,

    pub pretty_printer: PrettyPrinterConfig,
}

impl<'de> Deserialize<'de> for Config {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        enum Field {
            OutputPath,
            PrettyPrinter,
            Ignored,
        }

        struct FieldVisitor;

        impl<'de> serde::de::Visitor<'de> for FieldVisitor {
            type Value = Field;

            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                formatter.write_str("field identifier")
            }

            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    0u64 => Ok(Field::OutputPath),
                    1u64 => Ok(Field::PrettyPrinter),
                    _ => Ok(Field::Ignored),
                }
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    "output_path" => Ok(Field::OutputPath),
                    "pretty_printer" => Ok(Field::PrettyPrinter),
                    _ => Ok(Field::Ignored),
                }
            }

            fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    b"output_path" => Ok(Field::OutputPath),
                    b"pretty_printer" => Ok(Field::PrettyPrinter),
                    _ => Ok(Field::Ignored),
                }
            }
        }

        impl<'de> Deserialize<'de> for Field {
            #[inline]
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
            {
                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct Visitor<'de> {
            lifetime: PhantomData<&'de ()>,
        }

        impl<'de> serde::de::Visitor<'de> for Visitor<'de> {
            type Value = Config;

            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                formatter.write_str("struct Config")
            }

            #[inline]
            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::SeqAccess<'de>,
            {
                let output_path = seq
                    .next_element::<Utf8PathBuf>()?
                    .ok_or_else(|| {
                        serde::de::Error::invalid_length(0, &"struct Config with 2 elements")
                    })
                    .and_then(|path| path.canonicalize_utf8().map_err(serde::de::Error::custom))?;

                let pretty_printer = seq
                    .next_element::<PrettyPrinterConfig>()?
                    .unwrap_or_default();

                Ok(Config {
                    output_path,
                    pretty_printer,
                })
            }

            #[inline]
            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::MapAccess<'de>,
            {
                let mut output_path: Option<Utf8PathBuf> = None;
                let mut pretty_printer: Option<PrettyPrinterConfig> = None;

                while let Some(key) = map.next_key::<Field>()? {
                    match key {
                        Field::OutputPath if output_path.is_some() => {
                            return Err(serde::de::Error::duplicate_field("output_path"));
                        }

                        Field::OutputPath => {
                            output_path = Some(map.next_value::<Utf8PathBuf>()?);
                        }

                        Field::PrettyPrinter if pretty_printer.is_some() => {
                            return Err(serde::de::Error::duplicate_field("pretty_printer"));
                        }

                        Field::PrettyPrinter => {
                            pretty_printer = Some(map.next_value::<PrettyPrinterConfig>()?);
                        }

                        _ => {
                            map.next_value::<serde::de::IgnoredAny>()?;
                        }
                    }
                }
                let output_path = output_path
                    .ok_or_else(|| serde::de::Error::missing_field("output_path"))
                    .and_then(|path| path.canonicalize_utf8().map_err(serde::de::Error::custom))?;

                let pretty_printer = pretty_printer.unwrap_or_default();

                Ok(Config {
                    output_path,
                    pretty_printer,
                })
            }
        }

        const FIELDS: &[&str] = &["output_path", "pretty_printer"];

        deserializer.deserialize_struct(
            "Config",
            FIELDS,
            Visitor {
                lifetime: PhantomData,
            },
        )
    }
}

#[derive(Clone, Copy, Debug, Default, Deserialize)]
pub enum PrettyPrinterConfig {
    #[default]
    GloballyDerived,

    LocallyDerived,

    Static(beancount_pretty_printer::Config),
}