mod all_of;
mod any_of;
mod array;
mod boolean;
mod branch_result;
mod float;
mod integer;
mod local_date;
mod local_date_time;
mod local_time;
mod offset_date_time;
mod one_of;
mod string;
mod table;

pub use all_of::find_all_of_completion_items;
pub use any_of::find_any_of_completion_items;
use array::type_hint_array;
use boolean::type_hint_boolean;
use float::type_hint_float;
use integer::type_hint_integer;
use local_date::type_hint_local_date;
use local_date_time::type_hint_local_date_time;
use local_time::type_hint_local_time;
use offset_date_time::type_hint_offset_date_time;
pub use one_of::find_one_of_completion_items;
use string::type_hint_string;
use tombi_future::Boxable;
use tombi_schema_store::{
    Accessor, AnythingSchema, ArraySchema, BooleanSchema, CurrentSchema, FloatSchema,
    IntegerSchema, LocalDateSchema, LocalDateTimeSchema, LocalTimeSchema, OffsetDateTimeSchema,
    SchemaDefinitions, SchemaStore, SchemaUri, SchemaView, StringSchema, TableSchema,
};

use super::{
    CompletionCandidate, CompletionContent, CompletionHint, FindCompletionContents,
    schema_completion::SchemaCompletion,
};

impl FindCompletionContents for tombi_document_tree::Value {
    fn find_completion_contents<'a: 'b, 'b>(
        &'a self,
        position: tombi_text::Position,
        keys: &'a [tombi_document_tree::Key],
        accessors: &'a [Accessor],
        current_schema: Option<&'a CurrentSchema<'a>>,
        schema_context: &'a tombi_schema_store::SchemaContext<'a>,
        completion_hint: Option<CompletionHint>,
    ) -> tombi_future::BoxFuture<'b, Vec<CompletionContent>> {
        log::trace!("self = {:?}", self);
        log::trace!("accessors = {:?}", accessors);
        log::trace!("keys = {:?}", keys);
        log::trace!("current_schema = {:?}", current_schema);
        log::trace!("completion_hint = {:?}", completion_hint);

        async move {
            if let Some(Ok(current_schema)) = schema_context
                .get_subschema(accessors, current_schema)
                .await
            {
                return self
                    .find_completion_contents(
                        position,
                        keys,
                        accessors,
                        Some(&current_schema),
                        schema_context,
                        completion_hint,
                    )
                    .await;
            }

            let instance_type = tombi_schema_store::SchemaType::from_value_type(
                tombi_document_tree::ValueImpl::value_type(self),
            );
            let projected_schema = current_schema.and_then(|schema| {
                instance_type.and_then(|instance_type| {
                    schema.for_instance_type(instance_type, schema_context.string_formats())
                })
            });
            let current_schema = match current_schema {
                Some(schema) if schema.semantic_schema.is_some() && instance_type.is_some() => {
                    projected_schema.as_ref()
                }
                schema => schema,
            };

            match self {
                Self::Boolean(boolean) => {
                    boolean
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::Integer(integer) => {
                    integer
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::Float(float) => {
                    float
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::OffsetDateTime(offset_date_time) => {
                    offset_date_time
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::LocalDateTime(local_date_time) => {
                    local_date_time
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::LocalDate(local_date) => {
                    local_date
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::LocalTime(local_time) => {
                    local_time
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::String(string_value) => {
                    string_value
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::Array(array) => {
                    array
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::Table(table) => {
                    table
                        .find_completion_contents(
                            position,
                            keys,
                            accessors,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                }
                Self::Incomplete { .. } => match current_schema {
                    Some(current_schema) => {
                        SchemaCompletion
                            .find_completion_contents(
                                position,
                                keys,
                                accessors,
                                Some(current_schema),
                                schema_context,
                                completion_hint,
                            )
                            .await
                    }
                    None => {
                        let last_key = keys.last();

                        match (&last_key, completion_hint) {
                            (Some(last_key), Some(CompletionHint::EqualTrigger { range, .. }))
                                if range.end < position =>
                            {
                                vec![CompletionContent::new_type_hint_key(
                                    &last_key.value,
                                    last_key.range(),
                                    None,
                                    completion_hint,
                                )]
                            }
                            _ => type_hint_value(last_key, position, None, completion_hint),
                        }
                    }
                },
            }
        }
        .boxed()
    }
}

pub fn type_hint_value(
    key: Option<&tombi_document_tree::Key>,
    position: tombi_text::Position,
    schema_uri: Option<&SchemaUri>,
    completion_hint: Option<CompletionHint>,
) -> Vec<CompletionContent> {
    let mut completion_contents = itertools::concat([
        type_hint_boolean(position, schema_uri, completion_hint),
        type_hint_integer(position, schema_uri, completion_hint),
        type_hint_float(position, schema_uri, completion_hint),
        type_hint_string(position, schema_uri, completion_hint),
        type_hint_local_date_time(position, schema_uri, completion_hint),
        type_hint_local_date(position, schema_uri, completion_hint),
        type_hint_local_time(position, schema_uri, completion_hint),
        type_hint_offset_date_time(position, schema_uri, completion_hint),
        type_hint_array(position, schema_uri, completion_hint),
        vec![CompletionContent::new_type_hint_inline_table(
            position,
            schema_uri,
            completion_hint,
        )],
    ]);

    if let Some(key) = key {
        let need_key_hint = match completion_hint {
            Some(
                CompletionHint::DotTrigger { range, .. }
                | CompletionHint::EqualTrigger { range, .. },
            ) => range.end == position || range.end <= key.range().start,
            Some(
                CompletionHint::InTableHeader
                | CompletionHint::InArray { .. }
                | CompletionHint::Comma { .. },
            )
            | None => true,
        };
        if need_key_hint {
            completion_contents.push(CompletionContent::new_type_hint_key(
                &key.value,
                key.range(),
                schema_uri,
                completion_hint,
            ));
        }
    } else {
        completion_contents.push(CompletionContent::new_type_hint_empty_key(
            position,
            schema_uri,
            completion_hint,
        ))
    }

    completion_contents
}

impl CompletionCandidate for SchemaView {
    fn title<'a: 'b, 'b>(
        &'a self,
        schema_uri: &'a SchemaUri,
        definitions: &'a SchemaDefinitions,
        strict: Option<tombi_schema_type::BoolDefaultTrue>,
        schema_store: &'a SchemaStore,
        completion_hint: Option<CompletionHint>,
    ) -> tombi_future::BoxFuture<'b, Option<String>> {
        async move {
            match self {
                Self::Boolean(BooleanSchema { title, .. })
                | Self::Integer(IntegerSchema { title, .. })
                | Self::Float(FloatSchema { title, .. })
                | Self::String(StringSchema { title, .. })
                | Self::OffsetDateTime(OffsetDateTimeSchema { title, .. })
                | Self::LocalDateTime(LocalDateTimeSchema { title, .. })
                | Self::LocalDate(LocalDateSchema { title, .. })
                | Self::LocalTime(LocalTimeSchema { title, .. })
                | Self::Array(ArraySchema { title, .. })
                | Self::Table(TableSchema { title, .. })
                | Self::Anything(AnythingSchema { title, .. }) => {
                    title.as_deref().map(ToString::to_string)
                }
                Self::OneOf(one_of) => {
                    one_of
                        .title(
                            schema_uri,
                            definitions,
                            strict,
                            schema_store,
                            completion_hint,
                        )
                        .await
                }
                Self::AnyOf(any_of) => {
                    any_of
                        .title(
                            schema_uri,
                            definitions,
                            strict,
                            schema_store,
                            completion_hint,
                        )
                        .await
                }
                Self::AllOf(all_of) => {
                    all_of
                        .title(
                            schema_uri,
                            definitions,
                            strict,
                            schema_store,
                            completion_hint,
                        )
                        .await
                }
                Self::Nothing(_) | Self::Null => None,
            }
        }
        .boxed()
    }

    fn description<'a: 'b, 'b>(
        &'a self,
        schema_uri: &'a SchemaUri,
        definitions: &'a SchemaDefinitions,
        strict: Option<tombi_schema_type::BoolDefaultTrue>,
        schema_store: &'a SchemaStore,
        completion_hint: Option<CompletionHint>,
    ) -> tombi_future::BoxFuture<'b, Option<String>> {
        async move {
            match self {
                Self::Boolean(BooleanSchema { description, .. })
                | Self::Integer(IntegerSchema { description, .. })
                | Self::Float(FloatSchema { description, .. })
                | Self::String(StringSchema { description, .. })
                | Self::OffsetDateTime(OffsetDateTimeSchema { description, .. })
                | Self::LocalDateTime(LocalDateTimeSchema { description, .. })
                | Self::LocalDate(LocalDateSchema { description, .. })
                | Self::LocalTime(LocalTimeSchema { description, .. })
                | Self::Array(ArraySchema { description, .. })
                | Self::Table(TableSchema { description, .. })
                | Self::Anything(AnythingSchema { description, .. }) => {
                    description.as_deref().map(ToString::to_string)
                }
                Self::OneOf(one_of) => {
                    one_of
                        .description(
                            schema_uri,
                            definitions,
                            strict,
                            schema_store,
                            completion_hint,
                        )
                        .await
                }
                Self::AnyOf(any_of) => {
                    any_of
                        .description(
                            schema_uri,
                            definitions,
                            strict,
                            schema_store,
                            completion_hint,
                        )
                        .await
                }
                Self::AllOf(all_of) => {
                    all_of
                        .description(
                            schema_uri,
                            definitions,
                            strict,
                            schema_store,
                            completion_hint,
                        )
                        .await
                }
                Self::Nothing(_) | Self::Null => None,
            }
        }
        .boxed()
    }
}
