#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![warn(clippy::all, clippy::pedantic)]
#![allow(
    clippy::missing_errors_doc,
    clippy::cast_possible_truncation,
    clippy::cast_sign_loss
)]

use bytes::{BufMut, BytesMut};
use flexbuffers::VectorBuilder;
use heck::ToPascalCase;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{error::Error, str::FromStr};
use url::Url;
use uuid::Uuid;

/// Struct representation of a field in the `content.json`
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ContentField {
    /// field name
    pub name: String,
    /// JSON Value for the field. Type will
    /// be validated when uploaded.
    pub value: Value,
}

/// Struct representation of an object in the `content.json`
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ContentObject {
    /// specifies which content definition the object should
    /// be validated against.
    pub instance_of: String,
    /// fields to be validated and then stored for consumption
    /// by templates.
    pub fields: Vec<ContentField>,
}

/// Fields are used to describe properties on a model,
/// IO for actions/integrations and content definitions.
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Field {
    /// The index used for compact, vector based serialization.
    /// The index is preserved even if the property name changes.
    /// Each field must be assigned an index value from 0-255, with
    /// no gaps (i.e. cannot go from 2 to 4 without also having a field
    /// with an index of 3).
    pub idx: u8,
    /// this is the name that is used when accessing a property in a template
    /// and will be the name that is used in the JSON serialized format.
    ///
    /// ## Definition
    ///
    /// ```json
    /// {
    ///     "models": [
    ///         {
    ///             "name": "post",
    ///             "fields": [
    ///                 { "idx": 0, "name": "title", kind: "String" }
    ///             ]
    ///         }
    ///     ]
    /// }
    /// ```
    ///
    /// ## Template
    ///
    /// ```html
    /// <h1>{{ post.title }}</h1>
    /// ```
    ///
    /// ## JSON serialized results
    ///
    /// ```json
    /// {
    ///     "posts": [
    ///         { "title": "some title 1" },
    ///         { "title": "some title 2" },
    ///     ]
    /// }
    /// ```
    pub name: String,
    /// the "kind" represents the type of the value that will
    /// be stored or sent.
    pub kind: Kind,
    /// Used for models and content definitions. Determines whether
    /// you can access an object/item by this property. All items
    /// have are automatically stored ID that can be used for look
    /// up if no properties are indexed.
    ///
    /// Note: `indexed` fields also have a uniqueness constraint.
    ///
    /// !! Important: only `String`, `Uuid` and `Url` kinds can be indexed
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub indexed: Option<bool>,
    /// Specifies whether field can be queried via Gte/Gt, Lte/Lt, Eq and `BeginsWith`.
    ///
    /// !! Important: only `Bool`, `String`, `Url`, `Uuid`, `Timestamp`, `U8/16/32/64`, `I8/16/32/64`,
    /// !! and `F32/64` kinds can be made queryable.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub queryable: Option<bool>,
    /// Specifies whether the field can be searched by any value of the same kind.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub searchable: Option<bool>,
    /// Used for integrations, so that a property on the external
    /// resource's response can be renamed.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub mapping: Option<String>,
    /// Optional message to hint to the user
    /// in an editor.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub doc: Option<String>,

    // todo: configure with CompressionAlgorithm
    /// Compresses the value of the field using the Zstd compression algorithm (level 4).
    ///
    /// Note: at some point in the future, compression levels and compression algorithm
    /// selection will be configurable.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub compressed: Option<bool>,

    /// Encrypts the field using `XChaCha20Poly1305`.
    ///
    /// Note: The storage encryption key is stored on the same
    /// server as the data it's encrypting; anyone with full access
    /// to the server. This is mostly useful for protecting
    /// sensitive data in backups (assuming the key isn't stored with the
    /// backup), and can potentially limit exposure in partial-access context
    /// stemming from a misconfiguration or a mistake in a multi-tenant scenario
    /// (i.e. for whatever reason tenant A's database contents is moved to tenant B's
    /// directory, without the keys also being shifted over).
    ///
    /// !! Important: this is not currently intended for storing *any* meaningfully
    /// !! sensitive data (e.g. SSNs, credit card numbers, passwords, encryption keys, etc.)
    /// !! use (or don't) at your own risk.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub encrypted: Option<bool>,
}

impl Default for Field {
    fn default() -> Field {
        Field {
            idx: 0,
            name: "field".into(),
            kind: Kind::Void,
            indexed: None,
            queryable: None,
            searchable: None,
            doc: None,
            mapping: None,
            compressed: None,
            encrypted: None,
        }
    }
}

#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum TimeUnit {
    Seconds,
}

#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct EnumOpt {
    pub idx: u8,
    pub name: String,
}

/// Defines the types recognized by this framework.
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum Kind {
    /// In the case of one-to-many the deletion of the
    /// parent reference causes a cascading delete (i.e if you have a user, chat, message
    /// relationship structure, deletion of the user or the chat automatically
    /// deletes messages owned by either).
    ///
    /// References must always be bidirectionally defined.
    Ref {
        /// name of model you're referencing.
        model: String,
        /// name of the field on the model.
        field: String,

        /// whether you have many of the referenced model.
        #[serde(skip_serializing_if = "Option::is_none")]
        #[serde(default)]
        many: Option<bool>,
    },
    /// null/void/undefined/None
    Void,
    /// boolean/true/false
    Bool,
    /// integers between
    I8,
    /// integers between
    U8,
    /// integers between
    I16,
    /// integers between
    U16,
    /// integers between
    I32,
    /// integers between
    U32,
    /// integers between
    I64,
    /// integers between
    U64,
    /// floats between
    F32,
    /// floats between
    F64,
    /// universal unique identifiers. 16 bytes
    /// or 36 characters.
    Uuid,
    // todo: IpV4,
    // todo: IpV6,
    // todo: Email,
    /// must be a valid URL
    Url,
    /// unix timestamp
    Timestamp { unit: TimeUnit },
    // todo: Duration,
    /// array of bytes.
    Blob,
    /// text/string/characters.
    String,
    /// JSON formatted string.
    Json,
    // XML formatted string.
    // todo: Xml,
    /// Markdown formatted string
    Markdown,
    /// array of values.
    List {
        /// the type for the values stored in the list.
        #[cfg_attr(feature = "utoipa", schema(no_recursion))]
        kind: Box<Kind>,
    },
    /// Select from a list of options. Stored as its u8 index value.
    Enum { name: String, opts: Vec<EnumOpt> },
    /// complex kind with its own name and subfields.
    Object {
        /// the object definition's name.
        name: String,
        /// nested subfields.
        #[cfg_attr(feature = "utoipa", schema(no_recursion))]
        fields: Vec<Field>,
    },
}

impl Kind {
    #[allow(clippy::too_many_lines)]
    pub fn copy_to(
        &self,
        src: &flexbuffers::Reader<&[u8]>,
        dest: &mut VectorBuilder,
        size: Option<&mut usize>,
    ) -> Result<(), Box<dyn Error>> {
        match self {
            Self::Ref {
                model: _,
                field: _,
                many,
            } => {
                if many == &Some(true) {
                    let mut ref_ids = dest.start_vector();

                    if let Some(size) = size {
                        for ref_id in &src.as_vector() {
                            *size += ref_id.as_blob().0.len();
                            ref_ids.push(ref_id.as_blob());
                        }
                    } else {
                        for ref_id in &src.as_vector() {
                            ref_ids.push(ref_id.as_blob());
                        }
                    }
                } else {
                    if let Some(size) = size {
                        *size += src.as_blob().0.len();
                    }

                    dest.push(src.as_blob());
                }
            }
            Self::Void => (),
            Self::Bool => {
                if let Some(size) = size {
                    *size += 1;
                }

                let val = src.as_bool();
                dest.push(val);
            }
            Self::I8 => {
                if let Some(size) = size {
                    *size += 1;
                }

                let val = src.as_i8();
                dest.push(val);
            }
            Self::U8 => {
                if let Some(size) = size {
                    *size += 1;
                }

                let val = src.as_u8();
                dest.push(val);
            }
            Self::I16 => {
                if let Some(size) = size {
                    *size += 2;
                }

                let val = src.as_i16();
                dest.push(val);
            }
            Self::U16 => {
                if let Some(size) = size {
                    *size += 2;
                }

                let val = src.as_u16();
                dest.push(val);
            }
            Self::I32 => {
                if let Some(size) = size {
                    *size += 4;
                }

                let val = src.as_i32();
                dest.push(val);
            }
            Self::U32 => {
                if let Some(size) = size {
                    *size += 4;
                }

                let val = src.as_u32();
                dest.push(val);
            }
            Self::I64 => {
                if let Some(size) = size {
                    *size += 8;
                }

                let val = src.as_i64();
                dest.push(val);
            }
            Self::U64 => {
                if let Some(size) = size {
                    *size += 8;
                }

                let val = src.as_u64();
                dest.push(val);
            }
            Self::F32 => {
                if let Some(size) = size {
                    *size += 4;
                }

                let val = src.as_f32();
                dest.push(val);
            }
            Self::F64 => {
                if let Some(size) = size {
                    *size += 8;
                }

                let val = src.as_f64();
                dest.push(val);
            }
            Self::Timestamp { unit } => match unit {
                TimeUnit::Seconds => {
                    if let Some(size) = size {
                        *size += 8;
                    }

                    let val = src.as_i64();
                    dest.push(val);
                }
            },
            Self::Uuid => {
                if let Some(size) = size {
                    *size += 16;
                }

                let uuid = src.as_blob();

                if uuid.0.len() != 16 {
                    return Err("uuid is not len 16".into());
                }

                dest.push(uuid);
            }
            Self::Blob => {
                if let Some(size) = size {
                    *size += src.as_blob().0.len();
                }

                dest.push(src.as_blob());
            }
            Self::Url => {
                let val = src.as_str();
                if let Some(size) = size {
                    *size += val.len();
                }

                Url::parse(val)?;

                dest.push(val);
            }
            Self::String | Self::Json | Self::Markdown => {
                let val = src.as_str();
                if let Some(size) = size {
                    *size += val.len();
                }

                dest.push(val);
            }
            Self::List { kind } => {
                let mut list = dest.start_vector();

                if let Some(size) = size {
                    for item in &src.as_vector() {
                        kind.copy_to(&item, &mut list, Some(size))?;
                    }
                } else {
                    for item in &src.as_vector() {
                        kind.copy_to(&item, &mut list, None)?;
                    }
                }

                list.end_vector();
            }
            Self::Enum { name: _, opts } => {
                if let Some(size) = size {
                    *size += 1;
                }

                let val = src.as_u8();

                for opt in opts {
                    if val == opt.idx {
                        dest.push(val);
                        break;
                    }
                }
            }
            Self::Object { name: _, fields } => {
                let mut obj = dest.start_vector();
                let src_vec = src.as_vector();

                if let Some(size) = size {
                    for field in fields {
                        let item = src_vec.idx(field.idx as usize);
                        field.kind.copy_to(&item, &mut obj, Some(size))?;
                    }
                } else {
                    for field in fields {
                        let item = src_vec.idx(field.idx as usize);
                        field.kind.copy_to(&item, &mut obj, None)?;
                    }
                }

                obj.end_vector();
            }
        }

        Ok(())
    }

    #[must_use]
    pub fn to_stringified_type(&self, prefix: &str) -> (String, Option<(String, Vec<Field>)>) {
        match self {
            Self::Ref {
                model: _,
                field: _,
                many: _,
            } => {
                // todo
                ("()".to_string(), None)
            }
            Self::Void => ("()".to_string(), None),
            Self::Bool => ("bool".to_string(), None),
            Self::I8 => ("i8".to_string(), None),
            Self::Enum { name: _, opts: _ } | Self::U8 => ("u8".to_string(), None),
            Self::I16 => ("i16".to_string(), None),
            Self::U16 => ("u16".to_string(), None),
            Self::I32 => ("i32".to_string(), None),
            Self::U32 => ("u32".to_string(), None),
            Self::I64 => ("i64".to_string(), None),
            Self::U64 => ("u64".to_string(), None),
            Self::F32 => ("f32".to_string(), None),
            Self::F64 => ("f64".to_string(), None),
            Self::Timestamp { unit } => match unit {
                TimeUnit::Seconds => ("i64".to_string(), None),
            },
            Self::Uuid => ("[u8; 16]".to_string(), None),
            Self::Blob => ("Vec<u8>".to_string(), None),
            Self::String | Self::Json | Self::Markdown | Self::Url => ("String".to_string(), None),
            Self::List { kind } => {
                let (kind, fields) = kind.to_stringified_type(prefix);
                (format!("Vec<{kind}>"), fields)
            }
            Self::Object { name, fields } => {
                let struct_name = format!("{}{}", prefix, name.to_pascal_case());
                (struct_name.clone(), Some((struct_name, fields.clone())))
            }
        }
    }

    #[must_use]
    pub fn to_stringified_type_w_lifetime(
        &self,
        prefix: &str,
        lifetime: &str,
        cow_str: bool,
    ) -> (String, Option<(String, Vec<Field>)>) {
        match self {
            Self::Ref {
                model: _,
                field: _,
                many: _,
            } => {
                // todo
                ("()".to_string(), None)
            }
            Self::Void => ("()".to_string(), None),
            Self::Bool => ("bool".to_string(), None),
            Self::I8 => ("i8".to_string(), None),
            Self::Enum { name: _, opts: _ } | Self::U8 => ("u8".to_string(), None),
            Self::I16 => ("i16".to_string(), None),
            Self::U16 => ("u16".to_string(), None),
            Self::I32 => ("i32".to_string(), None),
            Self::U32 => ("u32".to_string(), None),
            Self::I64 => ("i64".to_string(), None),
            Self::U64 => ("u64".to_string(), None),
            Self::F32 => ("f32".to_string(), None),
            Self::F64 => ("f64".to_string(), None),
            Self::Timestamp { unit } => match unit {
                TimeUnit::Seconds => ("i64".to_string(), None),
            },
            Self::Uuid => ("[u8; 16]".to_string(), None),
            Self::Blob => (format!("&'{lifetime} [u8]"), None),
            Self::String | Self::Json | Self::Markdown | Self::Url => (
                if cow_str {
                    format!("std::borrow::Cow<'{lifetime}, str>")
                } else {
                    format!("&'{lifetime} str")
                },
                None,
            ),
            Self::List { kind } => {
                let (kind, fields) = kind.to_stringified_type_w_lifetime(prefix, lifetime, cow_str);
                (format!("Vec<{kind}>"), fields)
            }
            Self::Object { name, fields } => {
                let struct_name = format!("{}{}", prefix, name.to_pascal_case());
                (struct_name.clone(), Some((struct_name, fields.clone())))
            }
        }
    }

    /// based on <https://docs.rs/flexbuffers/latest/flexbuffers/struct.Reader.html>
    #[must_use]
    pub fn to_flexbuffer_accessor(&self) -> Option<&'static str> {
        match self {
            Self::Ref { .. } | Self::Void | Self::List { .. } | Self::Object { .. } => None,
            Self::Bool => Some(".as_bool()"),
            Self::I8 => Some(".as_i8()"),
            Self::Enum { .. } | Self::U8 => Some(".as_u8()"),
            Self::I16 => Some(".as_i16()"),
            Self::U16 => Some(".as_u16()"),
            Self::I32 => Some(".as_i32()"),
            Self::U32 => Some(".as_u32()"),
            Self::I64 => Some(".as_i64()"),
            Self::U64 => Some(".as_u64()"),
            Self::F32 => Some(".as_f32()"),
            Self::F64 => Some(".as_f64()"),
            Self::Timestamp { unit } => match unit {
                TimeUnit::Seconds => Some(".as_i64()"),
            },
            Self::Uuid => Some(".as_blob().0.try_into().unwrap_or([0u8; 16])"),
            Self::Blob => Some(".as_blob().0"),
            Self::String | Self::Json | Self::Markdown | Self::Url => Some(".as_str()"),
        }
    }
}

#[allow(clippy::too_many_lines)]
/// Validates, converts and pushes a JSON Value to a `FlexBuffer` vector.
pub fn json_to_flexbuffer_vec(
    kind: &Kind,
    value: &Value,
    vec: &mut VectorBuilder<'_>,
) -> Result<(), Box<dyn Error>> {
    match &value {
        Value::Null => {
            if let Kind::Void = kind {
                vec.push(());
            } else {
                return Err("should not be null".into());
            }
        }
        Value::Bool(v) => {
            if let Kind::Bool = kind {
                vec.push(*v);
            } else {
                return Err("got back invalid type for...".into());
            }
        }
        Value::Number(v) => match kind {
            Kind::I8 => {
                if let Some(number) = v.as_i64() {
                    vec.push(i8::try_from(number)?);
                } else {
                    return Err("not a i8".into());
                }
            }
            Kind::U8 => {
                if let Some(number) = v.as_u64() {
                    vec.push(u8::try_from(number)?);
                } else {
                    return Err("not a u8".into());
                }
            }
            Kind::I16 => {
                if let Some(number) = v.as_i64() {
                    vec.push(i16::try_from(number)?);
                } else {
                    return Err("not a i16".into());
                }
            }
            Kind::U16 => {
                if let Some(number) = v.as_u64() {
                    vec.push(u16::try_from(number)?);
                } else {
                    return Err("not a u16".into());
                }
            }
            Kind::I32 => {
                if let Some(number) = v.as_i64() {
                    vec.push(i32::try_from(number)?);
                } else {
                    return Err("not a i32".into());
                }
            }
            Kind::U32 => {
                if let Some(number) = v.as_u64() {
                    vec.push(u32::try_from(number)?);
                } else {
                    return Err("not a u32".into());
                }
            }
            Kind::I64 => {
                if let Some(number) = v.as_i64() {
                    vec.push(number);
                } else {
                    return Err("not a i64".into());
                }
            }
            Kind::U64 => {
                if let Some(number) = v.as_u64() {
                    vec.push(number);
                } else {
                    return Err("not a u64".into());
                }
            }
            Kind::F32 => {
                if let Some(number) = v.as_f64() {
                    if number <= f32::MAX.into() && number >= f32::MIN.into() {
                        vec.push(number as f32);
                    }
                } else {
                    return Err("not a f32".into());
                }
            }
            Kind::F64 => {
                if let Some(number) = v.as_f64() {
                    vec.push(number);
                } else {
                    return Err("not a f64".into());
                }
            }
            Kind::Timestamp { unit } => match unit {
                TimeUnit::Seconds => {
                    if let Some(number) = v.as_i64() {
                        vec.push(number);
                    } else {
                        return Err("not a i64".into());
                    }
                }
            },
            Kind::Enum { name, opts } => {
                if let Some(v) = v.as_i64() {
                    for opt in opts {
                        if v < 256 && v > -1 && v as u8 == opt.idx {
                            vec.push(opt.idx);
                            break;
                        }
                    }
                } else {
                    return Err(format!("not an i64 for {name}").into());
                }
            }
            _ => return Err("not supposed to be a number".into()),
        },
        Value::String(v) => match kind {
            Kind::String | Kind::Json | Kind::Markdown => {
                vec.push(v.as_str());
            }
            Kind::Url => {
                let val = v.as_str();
                Url::parse(val)?;
                vec.push(val);
            }
            Kind::Uuid => {
                let uuid = Uuid::from_str(v.as_str())?;
                vec.push(flexbuffers::Blob(uuid.as_ref()));
            }
            Kind::Enum { name: _, opts } => {
                for opt in opts {
                    if v == &opt.name {
                        vec.push(opt.idx);
                        break;
                    }
                }
            }
            _ => return Err("not destined for `kind` String".into()),
        },
        Value::Array(v) => match kind {
            Kind::List { kind } => {
                let mut vec = vec.start_vector();

                for value in v {
                    json_to_flexbuffer_vec(kind, value, &mut vec)?;
                }

                vec.end_vector();
            }
            Kind::Blob => {
                let mut blob = BytesMut::new();

                for value in v {
                    if let Some(val) = value.as_u64() {
                        let byte = val as u8;
                        blob.put_u8(byte);
                    }
                }

                vec.push(flexbuffers::Blob(blob.as_ref()));
            }
            Kind::Uuid => {
                let mut uuid_bytes = [0u8; 16];

                for (i, value) in v.iter().enumerate() {
                    if i < 16
                        && let Some(val) = value.as_u64()
                    {
                        let byte = val as u8;
                        uuid_bytes[i] = byte;
                    }
                }

                vec.push(flexbuffers::Blob(uuid_bytes.as_slice()));
            }
            Kind::Json => {
                if let Kind::Json = &kind {
                    let stringified = serde_json::to_string(&v)?;
                    vec.push(stringified.as_str());
                }
            }
            _ => return Err("not array type".into()),
        },
        Value::Object(v) => {
            if let Kind::Object { name, fields } = &kind {
                let mut sorted_fields = fields.clone();
                sorted_fields.sort_by(|a, b| a.idx.cmp(&b.idx));

                let mut object = vec.start_vector();

                for field in fields {
                    if let Some(val) = v.get(&field.name) {
                        json_to_flexbuffer_vec(&field.kind, val, &mut object)?;
                    } else {
                        return Err(format!(
                            "field '{}' doesn't exist on JSON for object '{}'",
                            field.name, name
                        )
                        .into());
                    }
                }

                object.end_vector();
            }

            if let Kind::Json = &kind {
                let stringified = serde_json::to_string(&v)?;
                vec.push(stringified.as_str());
            }
        }
    }

    Ok(())
}

pub fn flexbuffer_reader_to_string(
    kind: &Kind,
    val: &flexbuffers::Reader<&[u8]>,
) -> Result<String, Box<dyn Error>> {
    let out = match kind {
        Kind::Ref { .. } | Kind::Void => String::new(),
        Kind::Bool => format!("{}", val.as_bool()),
        Kind::U8 => format!("{}", val.as_u8()),
        Kind::I8 => format!("{}", val.as_i8()),
        Kind::U16 => format!("{}", val.as_u16()),
        Kind::I16 => format!("{}", val.as_i16()),
        Kind::U32 => format!("{}", val.as_u32()),
        Kind::I32 => format!("{}", val.as_i32()),
        Kind::U64 => format!("{}", val.as_u64()),
        Kind::I64 => format!("{}", val.as_i64()),
        Kind::Timestamp { unit } => match unit {
            TimeUnit::Seconds => format!("{}", val.as_i64()),
        },
        Kind::F32 => format!("{}", val.as_f32()),
        Kind::F64 => format!("{}", val.as_f64()),
        Kind::Uuid => Uuid::from_bytes(val.as_blob().0.try_into().unwrap_or([0u8; 16])).to_string(),
        Kind::Blob => Value::Array(
            // todo: b64 encode
            val.as_blob()
                .0
                .iter()
                .map(|v| Value::Number((*v).into()))
                .collect(),
        )
        .to_string(),
        Kind::Url => {
            let val = val.as_str();
            Url::parse(val)?;

            val.into()
        }
        Kind::String | Kind::Markdown | Kind::Json => val.as_str().into(),
        Kind::Enum { name: _, opts } => {
            let opt_idx = val.as_u8();

            for opt in opts {
                if opt.idx == opt_idx {
                    return Ok(opt.name.clone());
                }
            }

            String::new()
        }
        Kind::List { kind } => Value::Array({
            let mut out = vec![];

            for v in &val.as_vector() {
                out.push(flexbuffer_reader_to_json(kind, &v)?);
            }

            out
        })
        .to_string(),
        Kind::Object { name: _, fields } => {
            let mut fields = fields.clone();
            fields.sort_by(|a, b| a.idx.cmp(&b.idx));

            let mut map = serde_json::Map::new();

            for (i, v) in val.as_vector().iter().enumerate() {
                if let Some(field) = fields.get(i) {
                    map.insert(
                        field.name.clone(),
                        flexbuffer_reader_to_json(&field.kind, &v)?,
                    );
                }
            }

            Value::Object(map).to_string()
        }
    };

    Ok(out)
}

pub fn flexbuffer_reader_to_json(
    kind: &Kind,
    val: &flexbuffers::Reader<&[u8]>,
) -> Result<Value, Box<dyn Error>> {
    let out = match kind {
        Kind::Ref {
            model: _,
            field: _,
            many: _,
        } => {
            // todo
            Value::Null
        }
        Kind::Void => Value::Null,
        Kind::Bool => Value::Bool(val.as_bool()),
        Kind::U8 => Value::Number(val.as_u8().into()),
        Kind::I8 => Value::Number(val.as_i8().into()),
        Kind::U16 => Value::Number(val.as_u16().into()),
        Kind::I16 => Value::Number(val.as_i16().into()),
        Kind::U32 => Value::Number(val.as_u32().into()),
        Kind::I32 => Value::Number(val.as_i32().into()),
        Kind::U64 => Value::Number(val.as_u64().into()),
        Kind::I64 => Value::Number(val.as_i64().into()),
        Kind::Timestamp { unit } => match unit {
            TimeUnit::Seconds => Value::Number(val.as_i64().into()),
        },
        Kind::F32 => Value::Number(
            match serde_json::Number::from_f64(f64::from(val.as_f32())) {
                Some(v) => v,
                None => return Err("not a number".into()),
            },
        ),
        Kind::F64 => Value::Number(match serde_json::Number::from_f64(val.as_f64()) {
            Some(v) => v,
            None => return Err("not a number".into()),
        }),
        Kind::Uuid => Value::String(
            Uuid::from_bytes(val.as_blob().0.try_into().unwrap_or([0u8; 16])).to_string(),
        ),
        Kind::Blob => Value::Array(
            val.as_blob()
                .0
                .iter()
                .map(|v| Value::Number((*v).into()))
                .collect(),
        ),
        Kind::Url => {
            let val = val.as_str();
            Url::parse(val)?;

            Value::String(val.into())
        }
        Kind::String | Kind::Markdown => Value::String(val.as_str().into()),
        Kind::Json => match serde_json::from_str(val.as_str()) {
            Ok(val) => val,
            Err(_) => Value::String(val.as_str().into()),
        },
        Kind::Enum { name: _, opts } => {
            let opt_idx = val.as_u8();

            for opt in opts {
                if opt.idx == opt_idx {
                    return Ok(Value::String(opt.name.clone()));
                }
            }

            Value::Null
        }
        Kind::List { kind } => Value::Array({
            let mut out = vec![];

            for v in &val.as_vector() {
                out.push(flexbuffer_reader_to_json(kind, &v)?);
            }

            out
        }),
        Kind::Object { name: _, fields } => {
            let mut fields = fields.clone();
            fields.sort_by(|a, b| a.idx.cmp(&b.idx));

            let mut map = serde_json::Map::new();

            for (i, v) in val.as_vector().iter().enumerate() {
                if let Some(field) = fields.get(i) {
                    map.insert(
                        field.name.clone(),
                        flexbuffer_reader_to_json(&field.kind, &v)?,
                    );
                }
            }

            Value::Object(map)
        }
    };

    Ok(out)
}

pub fn flexbuffer_reader_to_path_component(
    kind: &Kind,
    val: &flexbuffers::Reader<&[u8]>,
) -> Result<String, Box<dyn Error>> {
    Ok(match kind {
        Kind::Ref {
            model: _,
            field: _,
            many: _,
        } => {
            // todo
            String::new()
        }
        Kind::Void => String::new(),
        Kind::Bool => format!("{}", val.as_bool()),
        Kind::U8 => format!("{}", val.as_u8()),
        Kind::I8 => format!("{}", val.as_i8()),
        Kind::U16 => format!("{}", val.as_u16()),
        Kind::I16 => format!("{}", val.as_i16()),
        Kind::U32 => format!("{}", val.as_u32()),
        Kind::I32 => format!("{}", val.as_i32()),
        Kind::U64 => format!("{}", val.as_u64()),
        Kind::I64 => format!("{}", val.as_i64()),
        Kind::Timestamp { unit } => match unit {
            TimeUnit::Seconds => format!("{}", val.as_i64()),
        },
        Kind::F32 => format!(
            "{}",
            match serde_json::Number::from_f64(f64::from(val.as_f32())) {
                Some(v) => v,
                None => return Err("not a number".into()),
            }
        ),
        Kind::F64 => format!(
            "{}",
            match serde_json::Number::from_f64(val.as_f64()) {
                Some(v) => v,
                None => return Err("not a number".into()),
            }
        ),
        Kind::Uuid => Uuid::from_bytes(val.as_blob().0.try_into().unwrap_or([0u8; 16])).to_string(),
        Kind::Blob => "TODO_BASE_64".to_string(),
        Kind::Url => "URI_INVALID_PATH_COMPONENT".to_string(),
        Kind::String | Kind::Markdown => val.as_str().into(),
        Kind::Json => match serde_json::from_str(val.as_str()) {
            Ok(val) => val,
            Err(_) => val.as_str().into(),
        },
        Kind::Enum { name: _, opts } => {
            let opt_idx = val.as_u8();

            for opt in opts {
                if opt.idx == opt_idx {
                    return Ok(opt.name.clone());
                }
            }

            String::new()
        }
        Kind::List { kind: _ } | Kind::Object { name: _, fields: _ } => "todo".to_string(),
    })
}
