use std::str::FromStr;

use indexmap::IndexSet;

mod generated;

mod definitions;
pub mod ids;
mod schemas;
mod string_literal;
mod types;
mod values;
pub mod writer;

pub use self::{
    definitions::{Definition, TypeDefinition},
    generated::{
        arguments::Argument,
        directives::{Directive, DirectiveDefinition},
        enums::{EnumDefinition, EnumValueDefinition},
        fields::FieldDefinition,
        input_objects::InputObjectDefinition,
        input_values::InputValueDefinition,
        interfaces::InterfaceDefinition,
        objects::ObjectDefinition,
        scalars::ScalarDefinition,
        schemas::{RootOperationTypeDefinition, SchemaDefinition},
        unions::UnionDefinition,
    },
    string_literal::{StringLiteral, StringLiteralKind},
    types::Type,
    values::Value,
};
use self::{ids::*, storage::DefinitionRecord};

#[derive(Default)]
pub struct TypeSystemDocument {
    strings: IndexSet<Box<str>>,
    block_strings: Vec<Box<str>>,

    definitions: Vec<storage::DefinitionRecord>,

    schema_definitions: Vec<storage::SchemaDefinitionRecord>,
    scalar_definitions: Vec<storage::ScalarDefinitionRecord>,
    object_definitions: Vec<storage::ObjectDefinitionRecord>,
    interface_definitions: Vec<storage::InterfaceDefinitionRecord>,
    union_definitions: Vec<storage::UnionDefinitionRecord>,
    enum_definitions: Vec<storage::EnumDefinitionRecord>,
    input_object_definitions: Vec<storage::InputObjectDefinitionRecord>,
    directive_definitions: Vec<storage::DirectiveDefinitionRecord>,

    root_operation_definitions: Vec<storage::RootOperationTypeDefinitionRecord>,

    field_definitions: Vec<storage::FieldDefinitionRecord>,
    input_value_definitions: Vec<storage::InputValueDefinitionRecord>,
    enum_value_definitions: Vec<storage::EnumValueDefinitionRecord>,

    type_references: Vec<storage::TypeRecord>,

    values: Vec<storage::ValueRecord>,
    directives: Vec<storage::DirectiveRecord>,
    arguments: Vec<storage::ArgumentRecord>,
}

#[derive(Clone, Copy, Debug)]
pub enum DirectiveLocation {
    Query,
    Mutation,
    Subscription,
    Field,
    FragmentDefinition,
    FragmentSpread,
    InlineFragment,
    Schema,
    Scalar,
    Object,
    FieldDefinition,
    ArgumentDefinition,
    Interface,
    Union,
    Enum,
    EnumValue,
    InputObject,
    InputFieldDefinition,
    VariableDefinition,
}

impl DirectiveLocation {
    pub fn as_str(self) -> &'static str {
        match self {
            DirectiveLocation::Query => "QUERY",
            DirectiveLocation::Mutation => "MUTATION",
            DirectiveLocation::Subscription => "SUBSCRIPTION",
            DirectiveLocation::Field => "FIELD",
            DirectiveLocation::FragmentDefinition => "FRAGMENT_DEFINITION",
            DirectiveLocation::FragmentSpread => "FRAGMENT_SPREAD",
            DirectiveLocation::InlineFragment => "INLINE_FRAGMENT",
            DirectiveLocation::Schema => "SCHEMA",
            DirectiveLocation::Scalar => "SCALAR",
            DirectiveLocation::Object => "OBJECT",
            DirectiveLocation::FieldDefinition => "FIELD_DEFINITION",
            DirectiveLocation::ArgumentDefinition => "ARGUMENT_DEFINITION",
            DirectiveLocation::Interface => "INTERFACE",
            DirectiveLocation::Union => "UNION",
            DirectiveLocation::Enum => "ENUM",
            DirectiveLocation::EnumValue => "ENUM_VALUE",
            DirectiveLocation::InputObject => "INPUT_OBJECT",
            DirectiveLocation::InputFieldDefinition => "INPUT_FIELD_DEFINITION",
            DirectiveLocation::VariableDefinition => "VARIABLE_DEFINITION",
        }
    }
}

impl FromStr for DirectiveLocation {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "QUERY" => DirectiveLocation::Query,
            "MUTATION" => DirectiveLocation::Mutation,
            "SUBSCRIPTION" => DirectiveLocation::Subscription,
            "FIELD" => DirectiveLocation::Field,
            "FRAGMENT_DEFINITION" => DirectiveLocation::FragmentDefinition,
            "FRAGMENT_SPREAD" => DirectiveLocation::FragmentSpread,
            "INLINE_FRAGMENT" => DirectiveLocation::InlineFragment,
            "SCHEMA" => DirectiveLocation::Schema,
            "SCALAR" => DirectiveLocation::Scalar,
            "OBJECT" => DirectiveLocation::Object,
            "FIELD_DEFINITION" => DirectiveLocation::FieldDefinition,
            "ARGUMENT_DEFINITION" => DirectiveLocation::ArgumentDefinition,
            "INTERFACE" => DirectiveLocation::Interface,
            "UNION" => DirectiveLocation::Union,
            "ENUM" => DirectiveLocation::Enum,
            "ENUM_VALUE" => DirectiveLocation::EnumValue,
            "INPUT_OBJECT" => DirectiveLocation::InputObject,
            "INPUT_FIELD_DEFINITION" => DirectiveLocation::InputFieldDefinition,
            "VARIABLE_DEFINITION" => DirectiveLocation::VariableDefinition,
            _ => return Err(()),
        })
    }
}

impl std::fmt::Display for DirectiveLocation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

pub trait TypeSystemId: Copy {
    type Reader<'a>: From<ReadContext<'a, Self>>;

    fn read(self, ast: &TypeSystemDocument) -> Self::Reader<'_> {
        ReadContext {
            id: self,
            document: ast,
        }
        .into()
    }
}

#[derive(Clone, Copy)]
pub struct ReadContext<'a, I> {
    id: I,
    document: &'a TypeSystemDocument,
}

impl super::TypeSystemDocument {
    pub fn read<T>(&self, id: T) -> T::Reader<'_>
    where
        T: TypeSystemId,
    {
        ReadContext { id, document: self }.into()
    }
}

impl TypeSystemDocument {
    pub fn definitions(&self) -> impl Iterator<Item = Definition<'_>> + '_ {
        self.definitions.iter().map(|definition| match definition {
            DefinitionRecord::Schema(id) => Definition::Schema(self.read(*id)),
            DefinitionRecord::Scalar(id) => {
                Definition::Type(TypeDefinition::Scalar(self.read(*id)))
            }
            DefinitionRecord::Object(id) => {
                Definition::Type(TypeDefinition::Object(self.read(*id)))
            }
            DefinitionRecord::Interface(id) => {
                Definition::Type(TypeDefinition::Interface(self.read(*id)))
            }
            DefinitionRecord::Union(id) => Definition::Type(TypeDefinition::Union(self.read(*id))),
            DefinitionRecord::Enum(id) => Definition::Type(TypeDefinition::Enum(self.read(*id))),
            DefinitionRecord::InputObject(id) => {
                Definition::Type(TypeDefinition::InputObject(self.read(*id)))
            }
            DefinitionRecord::SchemaExtension(id) => Definition::SchemaExtension(self.read(*id)),
            DefinitionRecord::ScalarExtension(id) => {
                Definition::TypeExtension(TypeDefinition::Scalar(self.read(*id)))
            }
            DefinitionRecord::ObjectExtension(id) => {
                Definition::TypeExtension(TypeDefinition::Object(self.read(*id)))
            }
            DefinitionRecord::InterfaceExtension(id) => {
                Definition::TypeExtension(TypeDefinition::Interface(self.read(*id)))
            }
            DefinitionRecord::UnionExtension(id) => {
                Definition::TypeExtension(TypeDefinition::Union(self.read(*id)))
            }
            DefinitionRecord::EnumExtension(id) => {
                Definition::TypeExtension(TypeDefinition::Enum(self.read(*id)))
            }
            DefinitionRecord::InputObjectExtension(id) => {
                Definition::TypeExtension(TypeDefinition::InputObject(self.read(*id)))
            }
            DefinitionRecord::Directive(id) => Definition::Directive(self.read(*id)),
        })
    }
}

pub mod storage {
    pub use super::{
        generated::{
            arguments::ArgumentRecord,
            directives::{DirectiveDefinitionRecord, DirectiveRecord},
            enums::EnumDefinitionRecord,
            enums::EnumValueDefinitionRecord,
            fields::FieldDefinitionRecord,
            input_objects::InputObjectDefinitionRecord,
            input_values::InputValueDefinitionRecord,
            interfaces::InterfaceDefinitionRecord,
            objects::ObjectDefinitionRecord,
            scalars::ScalarDefinitionRecord,
            schemas::{RootOperationTypeDefinitionRecord, SchemaDefinitionRecord},
            unions::UnionDefinitionRecord,
        },
        types::TypeRecord,
        values::ValueRecord,
    };

    use super::ids::*;

    #[derive(Clone, Copy)]
    pub enum DefinitionRecord {
        Schema(SchemaDefinitionId),
        Scalar(ScalarDefinitionId),
        Object(ObjectDefinitionId),
        Interface(InterfaceDefinitionId),
        Union(UnionDefinitionId),
        Enum(EnumDefinitionId),
        InputObject(InputObjectDefinitionId),
        SchemaExtension(SchemaDefinitionId),
        ScalarExtension(ScalarDefinitionId),
        ObjectExtension(ObjectDefinitionId),
        InterfaceExtension(InterfaceDefinitionId),
        UnionExtension(UnionDefinitionId),
        EnumExtension(EnumDefinitionId),
        InputObjectExtension(InputObjectDefinitionId),
        Directive(DirectiveDefinitionId),
    }
}
