use std::borrow::Cow;

use futures::future::join_all;
use itertools::Itertools;
use tombi_future::{BoxFuture, Boxable};
use tombi_schema_store::{
    Accessor, CurrentSchema, FindSchemaCandidates, PatternAccessor, Referable, SchemaAccessor,
    SchemaStore, TableSchema, ValueSchema, is_online_url,
};

use crate::{
    comment_directive::get_table_comment_directive_content_with_schema_uri,
    completion::{
        CompletionCandidate, CompletionContent, CompletionHint, FindCompletionContents,
        comment::get_tombi_comment_directive_content_completion_contents,
        value::{
            all_of::find_all_of_completion_items, any_of::find_any_of_completion_items,
            one_of::find_one_of_completion_items, type_hint_value,
        },
    },
    schema_resolver::resolve_table_unevaluated_property_schema,
};

impl FindCompletionContents for tombi_document_tree::Table {
    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>,
    ) -> BoxFuture<'b, Vec<CompletionContent>> {
        log::trace!("self = {:?}", self);
        log::trace!("keys = {:?}", keys);
        log::trace!("accessors = {:?}", accessors);
        log::trace!("current_schema = {:?}", current_schema);
        log::trace!("completion_hint = {:?}", completion_hint);

        async move {
            if keys.is_empty() {
                if let Some((comment_directive_context, schema_uri)) =
                    get_table_comment_directive_content_with_schema_uri(self, position, accessors)
                    && let Some(completions) =
                        get_tombi_comment_directive_content_completion_contents(
                            comment_directive_context,
                            schema_uri,
                        )
                        .await
                {
                    return completions;
                }

                if !matches!(
                    self.kind(),
                    tombi_document_tree::TableKind::InlineTable { .. }
                ) && completion_hint != Some(CompletionHint::InTableHeader)
                {
                    // Skip if the cursor is the end space of key value like:
                    //
                    // ```toml
                    // key = "value" █
                    // ```
                    for value in self.values() {
                        let end = value.range().end;
                        if end.line == position.line && end.column < position.column {
                            return vec![];
                        }
                    }
                }
            }

            // `range.end` points to the cursor position right after `}`.
            // At that point, completion should not behave as "inside inline table".
            if matches!(
                self.kind(),
                tombi_document_tree::TableKind::InlineTable { .. }
            ) && position >= self.range().end
            {
                return Vec::new();
            }

            if let Some(Ok(document_schema)) = schema_context
                .get_subschema(accessors, current_schema)
                .await
                && let Some(value_schema) = &document_schema.value_schema
            {
                return self
                    .find_completion_contents(
                        position,
                        keys,
                        accessors,
                        Some(&CurrentSchema {
                            value_schema: value_schema.clone(),
                            schema_uri: Cow::Borrowed(&document_schema.schema_uri),
                            definitions: Cow::Borrowed(&document_schema.definitions),
                        }),
                        schema_context,
                        completion_hint,
                    )
                    .await;
            }

            if let Some(current_schema) = current_schema {
                match current_schema.value_schema.as_ref() {
                    ValueSchema::Table(table_schema) => {
                        let mut completion_contents = Vec::new();

                        if let Some(key) = keys.first() {
                            let accessor_str = &key.value;
                            if let Some(value) = self.get(key) {
                                let accessor: Accessor = Accessor::Key(accessor_str.to_string());
                                let schema_accessor = SchemaAccessor::from(&accessor);
                                let need_magic_trigger = match completion_hint {
                                    Some(
                                        CompletionHint::DotTrigger { range, .. }
                                        | CompletionHint::EqualTrigger { range, .. },
                                    ) => range.end <= key.range().start,
                                    Some(
                                        CompletionHint::InArray { .. }
                                        | CompletionHint::InTableHeader
                                        | CompletionHint::Comma { .. },
                                    ) => false,
                                    None => true,
                                };

                                if table_schema
                                    .properties
                                    .read()
                                    .await
                                    .contains_key(&schema_accessor)
                                {
                                    if matches!(
                                        value,
                                        tombi_document_tree::Value::Incomplete { .. }
                                    ) && need_magic_trigger
                                    {
                                        return CompletionContent::new_magic_triggers(
                                            accessor_str,
                                            position,
                                            Some(current_schema.schema_uri.as_ref()),
                                        );
                                    }

                                    if let Ok(Some(current_schema)) = table_schema
                                        .resolve_property_schema(
                                            &schema_accessor,
                                            current_schema.schema_uri.clone(),
                                            current_schema.definitions.clone(),
                                            schema_context.store,
                                        )
                                        .await
                                    {
                                        log::trace!(
                                            "property_schema = {:?}",
                                            current_schema.value_schema
                                        );

                                        let mut contents = value
                                            .find_completion_contents(
                                                position,
                                                &keys[1..],
                                                &accessors
                                                    .iter()
                                                    .cloned()
                                                    .chain(std::iter::once(accessor))
                                                    .collect_vec(),
                                                Some(&current_schema),
                                                schema_context,
                                                completion_hint,
                                            )
                                            .await;

                                        if !contents.is_empty()
                                            && current_schema
                                                .value_schema
                                                .deprecation()
                                                .await
                                                .is_some()
                                        {
                                            for content in &mut contents {
                                                if !content.in_comment {
                                                    content.deprecated = Some(true);
                                                }
                                            }
                                        }

                                        return contents;
                                    }
                                } else {
                                    // When the key is not in the schema properties,
                                    // still try the value for comment directive handling.
                                    let contents = value
                                        .find_completion_contents(
                                            position,
                                            &keys[1..],
                                            &accessors
                                                .iter()
                                                .cloned()
                                                .chain(std::iter::once(accessor))
                                                .collect_vec(),
                                            None,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await
                                        .into_iter()
                                        .filter(|c| c.in_comment)
                                        .collect_vec();
                                    if !contents.is_empty() {
                                        return contents;
                                    }

                                    if keys.len() == 1 {
                                        let property_keys = table_schema
                                            .properties
                                            .read()
                                            .await
                                            .keys()
                                            .cloned()
                                            .collect_vec();
                                        for property_key in property_keys {
                                            let Some(key_name) = property_key.as_key() else {
                                                continue;
                                            };
                                            if !key_name.starts_with(accessor_str) {
                                                continue;
                                            }

                                            if let Some(value) = self.get(key_name)
                                                && check_used_table_value(
                                                    value,
                                                    accessors.is_empty(),
                                                    completion_hint,
                                                )
                                            {
                                                continue;
                                            }

                                            if let Ok(Some(current_schema)) = table_schema
                                                .resolve_property_schema(
                                                    &property_key,
                                                    current_schema.schema_uri.clone(),
                                                    current_schema.definitions.clone(),
                                                    schema_context.store,
                                                )
                                                .await
                                            {
                                                log::trace!(
                                                    "property_schema = {:?}",
                                                    &current_schema.value_schema
                                                );

                                                let Some(mut contents) =
                                                    collect_table_key_completion_contents(
                                                        self,
                                                        key_name,
                                                        position,
                                                        current_editing_key_range(keys, position),
                                                        accessors,
                                                        table_schema,
                                                        &current_schema,
                                                        schema_context,
                                                        completion_hint,
                                                    )
                                                    .await
                                                else {
                                                    continue;
                                                };

                                                if !contents.is_empty()
                                                    && current_schema
                                                        .value_schema
                                                        .deprecation()
                                                        .await
                                                        .is_some()
                                                {
                                                    for content in &mut contents {
                                                        if !content.in_comment {
                                                            content.deprecated = Some(true);
                                                        }
                                                    }
                                                }

                                                completion_contents.extend(contents);
                                            }
                                        }
                                    }
                                }

                                if !completion_contents.is_empty() {
                                    return completion_contents;
                                }

                                if let Some(pattern_properties) = &table_schema.pattern_properties {
                                    let pattern_keys = pattern_properties
                                        .read()
                                        .await
                                        .keys()
                                        .cloned()
                                        .collect_vec();
                                    for property_key in pattern_keys {
                                        let Ok(pattern) = tombi_regex::Regex::new(&property_key)
                                        else {
                                            log::warn!(
                                                "Invalid regex pattern property: {}",
                                                property_key
                                            );
                                            continue;
                                        };
                                        if pattern.is_match(accessor_str) {
                                            log::trace!(
                                                "pattern_property_schema = {:?}",
                                                &current_schema.value_schema
                                            );
                                            if let Ok(Some(current_schema)) = table_schema
                                                .resolve_pattern_property_schema(
                                                    &property_key,
                                                    current_schema.schema_uri.clone(),
                                                    current_schema.definitions.clone(),
                                                    schema_context.store,
                                                )
                                                .await
                                            {
                                                let mut contents =
                                                    get_property_value_completion_contents(
                                                        value,
                                                        position,
                                                        key,
                                                        keys,
                                                        accessors,
                                                        Some(&current_schema),
                                                        schema_context,
                                                        completion_hint,
                                                    )
                                                    .await;

                                                if !contents.is_empty()
                                                    && current_schema
                                                        .value_schema
                                                        .deprecation()
                                                        .await
                                                        .is_some()
                                                {
                                                    for content in &mut contents {
                                                        if !content.in_comment {
                                                            content.deprecated = Some(true);
                                                        }
                                                    }
                                                }

                                                return contents;
                                            }
                                        }
                                    }
                                }

                                if let Some((_, referable_additional_property_schema)) =
                                    &table_schema.additional_property_schema
                                {
                                    log::trace!(
                                        "additional_property_schema = {:?}",
                                        referable_additional_property_schema
                                    );

                                    if let Ok(Some(current_schema)) =
                                        tombi_schema_store::resolve_schema_item(
                                            referable_additional_property_schema,
                                            current_schema.schema_uri.clone(),
                                            current_schema.definitions.clone(),
                                            schema_context.store,
                                        )
                                        .await
                                    {
                                        let mut contents = get_property_value_completion_contents(
                                            value,
                                            position,
                                            key,
                                            keys,
                                            accessors,
                                            Some(&current_schema),
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;

                                        if !contents.is_empty()
                                            && current_schema
                                                .value_schema
                                                .deprecation()
                                                .await
                                                .is_some()
                                        {
                                            for content in &mut contents {
                                                if !content.in_comment {
                                                    content.deprecated = Some(true);
                                                }
                                            }
                                        }

                                        return contents;
                                    }
                                }

                                if let Some(one_of_schema) = table_schema.one_of.as_deref() {
                                    let completion_items =
                                        super::one_of::find_one_of_completion_items(
                                            self,
                                            position,
                                            keys,
                                            accessors,
                                            one_of_schema,
                                            current_schema,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;
                                    if !completion_items.is_empty() {
                                        return completion_items;
                                    }
                                }
                                if let Some(any_of_schema) = table_schema.any_of.as_deref() {
                                    let completion_items =
                                        super::any_of::find_any_of_completion_items(
                                            self,
                                            position,
                                            keys,
                                            accessors,
                                            any_of_schema,
                                            current_schema,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;
                                    if !completion_items.is_empty() {
                                        return completion_items;
                                    }
                                }
                                if let Some(all_of_schema) = table_schema.all_of.as_deref() {
                                    let completion_items =
                                        super::all_of::find_all_of_completion_items(
                                            self,
                                            position,
                                            keys,
                                            accessors,
                                            all_of_schema,
                                            current_schema,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;
                                    if !completion_items.is_empty() {
                                        return completion_items;
                                    }
                                }

                                if let Some(current_schema) =
                                    resolve_table_unevaluated_property_schema(
                                        table_schema,
                                        current_schema,
                                        schema_context,
                                    )
                                    .await
                                {
                                    let mut contents = get_property_value_completion_contents(
                                        value,
                                        position,
                                        key,
                                        keys,
                                        accessors,
                                        Some(&current_schema),
                                        schema_context,
                                        completion_hint,
                                    )
                                    .await;

                                    if !contents.is_empty()
                                        && current_schema.value_schema.deprecation().await.is_some()
                                    {
                                        for content in &mut contents {
                                            if !content.in_comment {
                                                content.deprecated = Some(true);
                                            }
                                        }
                                    }

                                    return contents;
                                }

                                if table_schema
                                    .allows_any_additional_properties(schema_context.strict())
                                {
                                    return get_property_value_completion_contents(
                                        value,
                                        position,
                                        key,
                                        keys,
                                        accessors,
                                        None,
                                        schema_context,
                                        completion_hint,
                                    )
                                    .await;
                                }
                            } else {
                                if let Some(one_of_schema) = table_schema.one_of.as_deref() {
                                    let completion_items =
                                        super::one_of::find_one_of_completion_items(
                                            self,
                                            position,
                                            keys,
                                            accessors,
                                            one_of_schema,
                                            current_schema,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;
                                    if !completion_items.is_empty() {
                                        return completion_items;
                                    }
                                }
                                if let Some(any_of_schema) = table_schema.any_of.as_deref() {
                                    let completion_items =
                                        super::any_of::find_any_of_completion_items(
                                            self,
                                            position,
                                            keys,
                                            accessors,
                                            any_of_schema,
                                            current_schema,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;
                                    if !completion_items.is_empty() {
                                        return completion_items;
                                    }
                                }
                                if let Some(all_of_schema) = table_schema.all_of.as_deref() {
                                    let completion_items =
                                        super::all_of::find_all_of_completion_items(
                                            self,
                                            position,
                                            keys,
                                            accessors,
                                            all_of_schema,
                                            current_schema,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;
                                    if !completion_items.is_empty() {
                                        return completion_items;
                                    }
                                }
                            }
                        } else {
                            let schema_accessors = table_schema
                                .properties
                                .read()
                                .await
                                .keys()
                                .cloned()
                                .collect_vec();
                            for schema_accessor in schema_accessors {
                                let Some(key_name) = schema_accessor.as_key() else {
                                    continue;
                                };

                                if let Some(value) = self.get(key_name)
                                    && check_used_table_value(
                                        value,
                                        accessors.is_empty(),
                                        completion_hint,
                                    )
                                {
                                    continue;
                                }

                                let online_ref_metadata = {
                                    let properties = table_schema.properties.read().await;
                                    properties
                                        .get(&schema_accessor)
                                        .and_then(|property_schema| {
                                            if let Referable::Ref {
                                                reference,
                                                title,
                                                description,
                                                deprecation,
                                                ..
                                            } = &property_schema.property_schema
                                                && is_online_url(reference)
                                            {
                                                Some((
                                                    title.clone(),
                                                    description.clone(),
                                                    deprecation.as_ref().map(|_| true),
                                                ))
                                            } else {
                                                None
                                            }
                                        })
                                };

                                if let Some((title, description, deprecated)) = online_ref_metadata
                                {
                                    completion_contents.push(CompletionContent::new_key(
                                        key_name,
                                        position,
                                        current_editing_key_range(keys, position),
                                        title,
                                        description,
                                        table_schema.required.as_ref(),
                                        Some(current_schema.schema_uri.as_ref()),
                                        deprecated,
                                        completion_hint,
                                        None,
                                    ));
                                    continue;
                                }

                                if let Ok(Some(current_schema)) = table_schema
                                    .resolve_property_schema(
                                        &schema_accessor,
                                        current_schema.schema_uri.clone(),
                                        current_schema.definitions.clone(),
                                        schema_context.store,
                                    )
                                    .await
                                {
                                    let Some(contents) = collect_table_key_completion_contents(
                                        self,
                                        key_name,
                                        position,
                                        current_editing_key_range(keys, position),
                                        accessors,
                                        table_schema,
                                        &current_schema,
                                        schema_context,
                                        completion_hint,
                                    )
                                    .await
                                    else {
                                        continue;
                                    };
                                    completion_contents.extend(contents)
                                }
                            }

                            if let Some(sub_schema_uri_map) = schema_context.sub_schema_uri_map {
                                for (root_accessors, sub_schema_uri) in sub_schema_uri_map {
                                    if let Some(last_key) =
                                        matching_subschema_completion_key(root_accessors, accessors)
                                    {
                                        let Ok(Some(document_schema)) = schema_context
                                            .store
                                            .try_get_document_schema(sub_schema_uri)
                                            .await
                                        else {
                                            continue;
                                        };
                                        let Some(value_schema) = &document_schema.value_schema
                                        else {
                                            continue;
                                        };

                                        let (schema_candidates, errors) = value_schema
                                            .find_schema_candidates(
                                                accessors,
                                                &document_schema.schema_uri,
                                                &document_schema.definitions,
                                                schema_context.store,
                                            )
                                            .await;

                                        for error in errors {
                                            log::warn!("{}", error);
                                        }

                                        completion_contents.push(CompletionContent::new_key(
                                            last_key,
                                            position,
                                            current_editing_key_range(keys, position),
                                            value_schema
                                                .detail(
                                                    &current_schema.schema_uri,
                                                    &current_schema.definitions,
                                                    schema_context.store,
                                                    completion_hint,
                                                )
                                                .await,
                                            value_schema
                                                .documentation(
                                                    &current_schema.schema_uri,
                                                    &current_schema.definitions,
                                                    schema_context.store,
                                                    completion_hint,
                                                )
                                                .await,
                                            None,
                                            Some(current_schema.schema_uri.as_ref()),
                                            value_schema.deprecation().await.map(|_| true),
                                            completion_hint,
                                            key_singleton_literal_label(&schema_candidates),
                                        ));
                                    }
                                }
                            }

                            if let Some(pattern_properties) = &table_schema.pattern_properties {
                                let patterns = pattern_properties
                                    .read()
                                    .await
                                    .keys()
                                    .map(ToString::to_string)
                                    .collect_vec();
                                completion_contents.push(CompletionContent::new_pattern_key(
                                    table_schema.additional_key_label.as_deref(),
                                    patterns.as_ref(),
                                    position,
                                    Some(current_schema.schema_uri.as_ref()),
                                    completion_hint,
                                ))
                            } else if let Some((_, additional_property_schema)) =
                                &table_schema.additional_property_schema
                                && let Ok(Some(CurrentSchema {
                                    value_schema,
                                    schema_uri,
                                    ..
                                })) = tombi_schema_store::resolve_schema_item(
                                    additional_property_schema,
                                    current_schema.schema_uri.clone(),
                                    current_schema.definitions.clone(),
                                    schema_context.store,
                                )
                                .await
                            {
                                completion_contents.push(CompletionContent::new_additional_key(
                                    table_schema.additional_key_label.as_deref(),
                                    position,
                                    Some(schema_uri.as_ref()),
                                    value_schema.deprecation().await.map(|_| true),
                                    completion_hint,
                                ));
                            }

                            // `allOf` schemas always apply alongside the direct
                            // properties, so their key completions must be merged in
                            // even when direct properties already produced candidates.
                            //
                            // `oneOf`/`anyOf` are intentionally NOT merged here: their
                            // branches are alternatives, and a key that is `required` in
                            // only one branch would otherwise be promoted to a global
                            // `required` key (mis-ordering it ahead of unconditionally
                            // required keys, e.g. pyproject's `version`/`dynamic` vs
                            // `name`). They are evaluated as a fallback only.
                            if let Some(all_of_schema) = table_schema.all_of.as_deref() {
                                let completion_items = super::all_of::find_all_of_completion_items(
                                    self,
                                    position,
                                    keys,
                                    accessors,
                                    all_of_schema,
                                    current_schema,
                                    schema_context,
                                    completion_hint,
                                )
                                .await;
                                completion_contents.extend(completion_items);
                            }

                            if completion_contents.is_empty() {
                                if let Some(one_of_schema) = table_schema.one_of.as_deref() {
                                    let completion_items =
                                        super::one_of::find_one_of_completion_items(
                                            self,
                                            position,
                                            keys,
                                            accessors,
                                            one_of_schema,
                                            current_schema,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;
                                    if !completion_items.is_empty() {
                                        return completion_items;
                                    }
                                }
                                if let Some(any_of_schema) = table_schema.any_of.as_deref() {
                                    let completion_items =
                                        super::any_of::find_any_of_completion_items(
                                            self,
                                            position,
                                            keys,
                                            accessors,
                                            any_of_schema,
                                            current_schema,
                                            schema_context,
                                            completion_hint,
                                        )
                                        .await;
                                    if !completion_items.is_empty() {
                                        return completion_items;
                                    }
                                }
                            }
                        }
                        crate::completion::dedup_completion_contents(completion_contents)
                    }
                    ValueSchema::OneOf(one_of_schema) => {
                        find_one_of_completion_items(
                            self,
                            position,
                            keys,
                            accessors,
                            one_of_schema,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                    }
                    ValueSchema::AnyOf(any_of_schema) => {
                        find_any_of_completion_items(
                            self,
                            position,
                            keys,
                            accessors,
                            any_of_schema,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                    }
                    ValueSchema::AllOf(all_of_schema) => {
                        find_all_of_completion_items(
                            self,
                            position,
                            keys,
                            accessors,
                            all_of_schema,
                            current_schema,
                            schema_context,
                            completion_hint,
                        )
                        .await
                    }
                    _ => Vec::new(),
                }
            } else if let Some(key) = keys.first() {
                if let Some(value) = self.get(key) {
                    get_property_value_completion_contents(
                        value,
                        position,
                        key,
                        keys,
                        accessors,
                        None,
                        schema_context,
                        completion_hint,
                    )
                    .await
                } else {
                    Vec::new()
                }
            } else {
                vec![CompletionContent::new_type_hint_empty_key(
                    position,
                    None,
                    completion_hint,
                )]
            }
        }
        .boxed()
    }
}

impl FindCompletionContents for TableSchema {
    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>,
    ) -> BoxFuture<'b, Vec<CompletionContent>> {
        log::trace!("self = {:?}", self);
        log::trace!("position = {:?}", position);
        log::trace!("keys = {:?}", keys);
        log::trace!("accessors = {:?}", accessors);
        log::trace!("current_schema = {:?}", current_schema);
        log::trace!("completion_hint = {:?}", completion_hint);

        async move {
            let Some(current_schema) = current_schema else {
                unreachable!("schema must be provided");
            };

            let mut completion_items = Vec::new();

            let property_keys = self.properties.read().await.keys().cloned().collect_vec();
            for key in property_keys {
                let Some(label) = key.as_key() else {
                    continue;
                };
                let current_schema = match self
                    .resolve_property_schema(
                        &key,
                        current_schema.schema_uri.clone(),
                        current_schema.definitions.clone(),
                        schema_context.store,
                    )
                    .await
                {
                    Ok(Some(current_schema)) => current_schema,
                    Ok(None) => continue,
                    Err(err) => {
                        log::warn!("{err}");
                        continue;
                    }
                };

                let (schema_candidates, errors) = current_schema
                    .value_schema
                    .find_schema_candidates(
                        accessors,
                        &current_schema.schema_uri,
                        &current_schema.definitions,
                        schema_context.store,
                    )
                    .await;

                for error in errors {
                    log::warn!("{}", error);
                }

                let singleton_value_label = key_singleton_literal_label(&schema_candidates);
                for schema_candidate in schema_candidates {
                    if let Some(CompletionHint::InTableHeader) = completion_hint
                        && count_table_or_array_schema(&current_schema, schema_context.store).await
                            == 0
                    {
                        continue;
                    }

                    completion_items.push(CompletionContent::new_key(
                        label,
                        position,
                        current_editing_key_range(keys, position),
                        schema_candidate
                            .detail(
                                &current_schema.schema_uri,
                                &current_schema.definitions,
                                schema_context.store,
                                completion_hint,
                            )
                            .await,
                        schema_candidate
                            .documentation(
                                &current_schema.schema_uri,
                                &current_schema.definitions,
                                schema_context.store,
                                completion_hint,
                            )
                            .await,
                        self.required.as_ref(),
                        Some(current_schema.schema_uri.as_ref()),
                        current_schema
                            .value_schema
                            .deprecation()
                            .await
                            .map(|_| true),
                        completion_hint,
                        singleton_value_label.clone(),
                    ));
                }
            }

            completion_items.push(CompletionContent::new_type_hint_inline_table(
                position,
                Some(current_schema.schema_uri.as_ref()),
                completion_hint,
            ));

            completion_items
        }
        .boxed()
    }
}

async fn count_table_or_array_schema(
    current_schema: &CurrentSchema<'_>,
    schema_store: &SchemaStore,
) -> usize {
    join_all(
        current_schema
            .value_schema
            .match_flattened_schemas(
                &|schema| matches!(schema, ValueSchema::Table(_) | ValueSchema::Array(_)),
                &current_schema.schema_uri,
                &current_schema.definitions,
                schema_store,
            )
            .await
            .into_iter()
            .map(|schema| async {
                match schema {
                    ValueSchema::Array(array_schema) => {
                        if let Some(item) = array_schema.items
                            && let Ok(Some(CurrentSchema {
                                schema_uri,
                                value_schema,
                                definitions,
                            })) = tombi_schema_store::resolve_schema_item(
                                &item,
                                Cow::Borrowed(&current_schema.schema_uri),
                                Cow::Borrowed(&current_schema.definitions),
                                schema_store,
                            )
                            .await
                        {
                            return value_schema
                                .is_match(
                                    &|schema| matches!(schema, ValueSchema::Table(_)),
                                    &schema_uri,
                                    &definitions,
                                    schema_store,
                                )
                                .await;
                        }
                        true
                    }
                    ValueSchema::Table(_) => true,
                    _ => unreachable!("only table and array are allowed"),
                }
            }),
    )
    .await
    .into_iter()
    .filter(|&is_table_or_array_schema| is_table_or_array_schema)
    .count()
}

fn get_property_value_completion_contents<'a: 'b, 'b>(
    value: &'a tombi_document_tree::Value,
    position: tombi_text::Position,
    key: &'a tombi_document_tree::Key,
    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>,
) -> BoxFuture<'b, Vec<CompletionContent>> {
    log::trace!("key = {:?}", key);
    log::trace!("value = {:?}", value);
    log::trace!("keys = {:?}", keys);
    log::trace!("accessors = {:?}", accessors);
    log::trace!("current_schema = {:?}", current_schema);
    log::trace!("completion_hint = {:?}", completion_hint);

    async move {
        if keys.len() == 1 {
            match completion_hint {
                Some(
                    CompletionHint::DotTrigger { range, .. }
                    | CompletionHint::EqualTrigger { range, .. },
                ) => {
                    let key = keys.first().unwrap();
                    if current_schema.is_none() {
                        if range.end <= key.range().start {
                            return vec![CompletionContent::new_type_hint_key(
                                &key.value,
                                key.range(),
                                None,
                                completion_hint,
                            )];
                        }
                        return type_hint_value(Some(key), position, None, completion_hint);
                    }
                }
                Some(CompletionHint::InTableHeader) => {
                    if let Some(current_schema) = current_schema
                        && count_table_or_array_schema(current_schema, schema_context.store).await
                            == 0
                    {
                        return Vec::new();
                    }
                }
                Some(CompletionHint::InArray { .. } | CompletionHint::Comma { .. }) | None => {
                    if matches!(value, tombi_document_tree::Value::Incomplete { .. }) {
                        if current_schema.is_none()
                            && matches!(completion_hint, Some(CompletionHint::InArray { .. }))
                        {
                            return vec![CompletionContent::new_type_hint_key(
                                &key.value,
                                key.range(),
                                None,
                                completion_hint,
                            )];
                        }

                        return CompletionContent::new_magic_triggers(
                            &key.value,
                            position,
                            current_schema.map(|schema| schema.schema_uri.as_ref()),
                        );
                    }
                }
            }
        }

        value
            .find_completion_contents(
                position,
                &keys[1..],
                &accessors
                    .iter()
                    .cloned()
                    .chain(std::iter::once(Accessor::Key(key.value.clone())))
                    .collect_vec(),
                current_schema,
                schema_context,
                completion_hint,
            )
            .await
    }
    .boxed()
}

fn check_used_table_value(
    value: &tombi_document_tree::Value,
    is_root: bool,
    completion_hint: Option<CompletionHint>,
) -> bool {
    match value {
        tombi_document_tree::Value::Boolean(_)
        | tombi_document_tree::Value::Integer(_)
        | tombi_document_tree::Value::Float(_)
        | tombi_document_tree::Value::String(_)
        | tombi_document_tree::Value::OffsetDateTime(_)
        | tombi_document_tree::Value::LocalDateTime(_)
        | tombi_document_tree::Value::LocalDate(_)
        | tombi_document_tree::Value::LocalTime(_) => return true,
        tombi_document_tree::Value::Array(array) => {
            if array.kind() == tombi_document_tree::ArrayKind::Array {
                return true;
            }
        }
        tombi_document_tree::Value::Table(table) => {
            if matches!(
                table.kind(),
                tombi_document_tree::TableKind::InlineTable { .. }
            ) || (is_root
                && completion_hint.is_none()
                && table.kind() == tombi_document_tree::TableKind::Table)
            {
                return true;
            }
        }
        tombi_document_tree::Value::Incomplete { .. } => {}
    }
    false
}

fn table_schema_has_remaining_key_completion<'a>(
    table: &'a tombi_document_tree::Table,
    table_schema: &'a TableSchema,
    schema_uri: Cow<'a, tombi_schema_store::SchemaUri>,
    definitions: Cow<'a, tombi_schema_store::SchemaDefinitions>,
    schema_store: &'a SchemaStore,
    strict: bool,
) -> BoxFuture<'a, bool> {
    async move {
        if table_schema.allows_any_additional_properties(strict) {
            return true;
        }

        let property_keys = table_schema
            .properties
            .read()
            .await
            .keys()
            .cloned()
            .collect_vec();
        for property_key in property_keys {
            let Some(key_name) = property_key.as_key() else {
                continue;
            };

            let Some(value) = table.get(key_name) else {
                return true;
            };

            let tombi_document_tree::Value::Table(table) = value else {
                continue;
            };

            let Ok(Some(current_schema)) = table_schema
                .resolve_property_schema(
                    &property_key,
                    Cow::Borrowed(&schema_uri),
                    Cow::Borrowed(&definitions),
                    schema_store,
                )
                .await
            else {
                return true;
            };

            let (schema_candidates, errors) = current_schema
                .value_schema
                .find_schema_candidates(
                    &[],
                    &current_schema.schema_uri,
                    &current_schema.definitions,
                    schema_store,
                )
                .await;

            if !errors.is_empty() {
                for error in errors {
                    log::warn!("{}", error);
                }
                return true;
            }

            for schema_candidate in schema_candidates {
                if let ValueSchema::Table(nested_table_schema) = schema_candidate
                    && table_schema_has_remaining_key_completion(
                        table,
                        &nested_table_schema,
                        Cow::Borrowed(&current_schema.schema_uri),
                        Cow::Borrowed(&current_schema.definitions),
                        schema_store,
                        strict,
                    )
                    .await
                {
                    return true;
                }
            }
        }

        false
    }
    .boxed()
}

fn collect_table_key_completion_contents<'a: 'b, 'b>(
    table: &'a tombi_document_tree::Table,
    key_name: &'a str,
    position: tombi_text::Position,
    replace_range: Option<tombi_text::Range>,
    accessors: &'a [Accessor],
    table_schema: &'a TableSchema,
    current_schema: &'a CurrentSchema<'a>,
    schema_context: &'a tombi_schema_store::SchemaContext<'a>,
    completion_hint: Option<CompletionHint>,
) -> BoxFuture<'b, Option<Vec<CompletionContent>>> {
    async move {
        let mut completion_contents = Vec::new();

        let (schema_candidates, errors) = current_schema
            .value_schema
            .find_schema_candidates(
                accessors,
                &current_schema.schema_uri,
                &current_schema.definitions,
                schema_context.store,
            )
            .await;

        for error in errors {
            log::warn!("{}", error);
        }

        let singleton_value_label = key_singleton_literal_label(&schema_candidates);
        for schema_candidate in schema_candidates {
            match &schema_candidate {
                ValueSchema::Boolean(_)
                | ValueSchema::Integer(_)
                | ValueSchema::Float(_)
                | ValueSchema::String(_)
                | ValueSchema::OffsetDateTime(_)
                | ValueSchema::LocalDateTime(_)
                | ValueSchema::LocalDate(_)
                | ValueSchema::LocalTime(_) => {
                    if matches!(completion_hint, Some(CompletionHint::InTableHeader))
                        || table.contains_key(key_name)
                    {
                        return None;
                    }
                }
                ValueSchema::Array(_) => {
                    if matches!(completion_hint, Some(CompletionHint::InTableHeader))
                        && count_table_or_array_schema(current_schema, schema_context.store).await
                            == 0
                    {
                        return None;
                    }
                }
                ValueSchema::Table(table_schema) => {
                    if matches!(completion_hint, Some(CompletionHint::InTableHeader))
                        && count_table_or_array_schema(current_schema, schema_context.store).await
                            == 0
                    {
                        return None;
                    }
                    if let Some(tombi_document_tree::Value::Table(table)) = table.get(key_name)
                        && !table_schema_has_remaining_key_completion(
                            table,
                            table_schema,
                            Cow::Borrowed(&current_schema.schema_uri),
                            Cow::Borrowed(&current_schema.definitions),
                            schema_context.store,
                            schema_context.strict(),
                        )
                        .await
                    {
                        return None;
                    }
                }
                ValueSchema::Anything(_) => {}
                ValueSchema::Nothing(_) | ValueSchema::Null => continue,
                ValueSchema::OneOf(_) | ValueSchema::AnyOf(_) | ValueSchema::AllOf(_) => {
                    unreachable!("OneOf, AnyOf, and AllOf are not allowed in flattened schema");
                }
            }

            completion_contents.push(CompletionContent::new_key(
                key_name,
                position,
                replace_range,
                schema_candidate
                    .detail(
                        &current_schema.schema_uri,
                        &current_schema.definitions,
                        schema_context.store,
                        completion_hint,
                    )
                    .await,
                schema_candidate
                    .documentation(
                        &current_schema.schema_uri,
                        &current_schema.definitions,
                        schema_context.store,
                        completion_hint,
                    )
                    .await,
                table_schema.required.as_ref(),
                Some(&current_schema.schema_uri),
                current_schema
                    .value_schema
                    .deprecation()
                    .await
                    .map(|_| true),
                completion_hint,
                singleton_value_label.clone(),
            ));
        }

        Some(completion_contents)
    }
    .boxed()
}

fn key_singleton_literal_label(schema_candidates: &[ValueSchema]) -> Option<String> {
    let labels = schema_candidates
        .iter()
        .map(|schema_candidate| {
            fn singleton_label<T>(
                const_value: &Option<T>,
                enum_values: &Option<Vec<T>>,
                format: impl Fn(&T) -> String,
            ) -> Option<String> {
                if let Some(const_value) = const_value {
                    return Some(format(const_value));
                }

                enum_values.as_ref().and_then(|values| {
                    if values.len() == 1 {
                        values.first().map(format)
                    } else {
                        None
                    }
                })
            }

            match schema_candidate {
                ValueSchema::String(schema) => {
                    singleton_label(&schema.const_value, &schema.r#enum, |v| format!("\"{v}\""))
                }
                ValueSchema::Boolean(schema) => {
                    singleton_label(&schema.const_value, &schema.r#enum, |v| v.to_string())
                }
                ValueSchema::Integer(schema) => {
                    singleton_label(&schema.const_value, &schema.r#enum, |v| v.to_string())
                }
                ValueSchema::Float(schema) => {
                    singleton_label(&schema.const_value, &schema.r#enum, |v| v.to_string())
                }
                ValueSchema::LocalDate(schema) => {
                    singleton_label(&schema.const_value, &schema.r#enum, |v| v.to_string())
                }
                ValueSchema::LocalDateTime(schema) => {
                    singleton_label(&schema.const_value, &schema.r#enum, |v| v.to_string())
                }
                ValueSchema::OffsetDateTime(schema) => {
                    singleton_label(&schema.const_value, &schema.r#enum, |v| v.to_string())
                }
                ValueSchema::LocalTime(_)
                | ValueSchema::Array(_)
                | ValueSchema::Table(_)
                | ValueSchema::OneOf(_)
                | ValueSchema::AnyOf(_)
                | ValueSchema::AllOf(_)
                | ValueSchema::Null
                | ValueSchema::Anything(_)
                | ValueSchema::Nothing(_) => None,
            }
        })
        .collect_vec();

    if labels.iter().any(Option::is_none) {
        return None;
    }

    let unique_labels = labels
        .into_iter()
        .flatten()
        .collect::<tombi_hashmap::HashSet<_>>();

    if unique_labels.len() == 1 {
        unique_labels.into_iter().next()
    } else {
        None
    }
}

fn matching_subschema_completion_key<'a>(
    root_accessors: &'a [PatternAccessor],
    accessors: &[Accessor],
) -> Option<&'a str> {
    let (PatternAccessor::Key(last_key), head_accessors) = root_accessors.split_last()? else {
        return None;
    };

    (head_accessors == accessors).then_some(last_key.as_str())
}

fn current_editing_key_range(
    keys: &[tombi_document_tree::Key],
    position: tombi_text::Position,
) -> Option<tombi_text::Range> {
    keys.last().and_then(|key| {
        let range = key.range();
        (range.start <= position && position <= range.end).then_some(range)
    })
}

#[cfg(test)]
mod tests {
    use tombi_schema_store::{Accessor, PatternAccessor};

    use super::matching_subschema_completion_key;

    #[test]
    fn wildcard_subschema_root_matches_completion_parent_path() {
        let root_accessors = vec![
            PatternAccessor::Key("tool".to_string()),
            PatternAccessor::AnyKey,
            PatternAccessor::Key("enabled".to_string()),
        ];
        let accessors = vec![
            Accessor::Key("tool".to_string()),
            Accessor::Key("taskipy".to_string()),
        ];

        assert_eq!(
            matching_subschema_completion_key(&root_accessors, &accessors),
            Some("enabled")
        );
    }

    #[test]
    fn wildcard_leaf_is_not_exposed_as_completion_key() {
        let root_accessors = vec![
            PatternAccessor::Key("tool".to_string()),
            PatternAccessor::Key("taskipy".to_string()),
            PatternAccessor::AnyKey,
        ];
        let accessors = vec![
            Accessor::Key("tool".to_string()),
            Accessor::Key("taskipy".to_string()),
        ];

        assert_eq!(
            matching_subschema_completion_key(&root_accessors, &accessors),
            None
        );
    }
}
