use std::cell::{Cell, RefCell};

use smallvec::SmallVec;

use oxc_ast::ast::Expression;
use oxc_span::GetSpan;

use super::chain_member::ChainMember;
use crate::formatter::{Format, JsFormatter, prelude::*};

/// Sized for the common cases: `a.b()` (3 members) and `a.b.c()` (4).
/// Longer chains spill to the heap, same as `Vec` did before.
pub(super) type ChainMembers<'a, 'b> = SmallVec<[ChainMember<'a, 'b>; 4]>;
type Groups<'a, 'b> = SmallVec<[MemberChainGroup<'a, 'b>; 4]>;

#[derive(Default)]
pub(super) struct MemberChainGroupsBuilder<'a, 'b> {
    /// keeps track of the groups created
    groups: Groups<'a, 'b>,
    /// keeps track of the current group that is being created/updated
    current_group: Option<MemberChainGroup<'a, 'b>>,
}

impl<'a, 'b> MemberChainGroupsBuilder<'a, 'b> {
    /// starts a new group
    pub fn start_group(&mut self, member: ChainMember<'a, 'b>) {
        debug_assert!(self.current_group.is_none());
        let mut group = MemberChainGroup::default();
        group.members.push(member);
        self.current_group = Some(group);
    }

    /// continues of starts a new group
    pub fn start_or_continue_group(&mut self, member: ChainMember<'a, 'b>) {
        match &mut self.current_group {
            None => self.start_group(member),
            Some(group) => group.members.push(member),
        }
    }

    /// clears the current group, and adds it to the groups collection
    pub fn close_group(&mut self) {
        if let Some(group) = self.current_group.take() {
            self.groups.push(group);
        }
    }

    pub(super) fn finish(self) -> TailChainGroups<'a, 'b> {
        let mut groups = self.groups;

        if let Some(group) = self.current_group {
            groups.push(group);
        }

        TailChainGroups { groups }
    }
}

/// Groups following on the head group.
///
/// May be empty if all members are part of the head group
#[derive(Debug)]
pub(super) struct TailChainGroups<'a, 'b> {
    groups: Groups<'a, 'b>,
}

impl<'a, 'b> TailChainGroups<'a, 'b> {
    /// Returns `true` if there are no tail groups.
    pub(crate) fn is_empty(&self) -> bool {
        self.groups.is_empty()
    }

    /// Returns the number of tail groups.
    pub(crate) fn len(&self) -> usize {
        self.groups.len()
    }

    /// Returns the first group
    pub(crate) fn first(&self) -> Option<&MemberChainGroup<'a, 'b>> {
        self.groups.first()
    }

    /// Returns the last group
    pub(crate) fn last(&self) -> Option<&MemberChainGroup<'a, 'b>> {
        self.groups.last()
    }

    /// Removes the first group and returns it
    pub(super) fn pop_first(&mut self) -> Option<MemberChainGroup<'a, 'b>> {
        if self.groups.is_empty() { None } else { Some(self.groups.remove(0)) }
    }

    /// Here we check if the length of the groups exceeds the cutoff or there are comments
    /// This function is the inverse of the prettier function
    /// [Prettier applies]: <https://github.com/prettier/prettier/blob/a043ac0d733c4d53f980aa73807a63fc914f23bd/src/language-js/print/member-chain.js#L342>
    pub(crate) fn is_member_call_chain(&self) -> bool {
        self.groups.len() > 1
    }

    /// Returns an iterator over the groups.
    pub(super) fn iter(&self) -> impl Iterator<Item = &MemberChainGroup<'a, 'b>> {
        self.groups.iter()
    }

    /// Test if any group except the last group [break](FormatElements::will_break).
    pub(super) fn any_except_last_will_break(&self, f: &JsFormatter<'_, 'a>) -> bool {
        let count = self.groups.len().saturating_sub(1);
        for group in self.groups.iter().take(count) {
            if group.will_break(f) {
                return true;
            }
        }

        false
    }

    /// Returns an iterator over all members
    pub(super) fn members(&self) -> impl Iterator<Item = &ChainMember<'a, 'b>> {
        self.groups.iter().flat_map(|group| group.members().iter())
    }
}

impl<'a> Format<'a, JsFormatContext<'a>> for TailChainGroups<'a, '_> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        f.join().entries(self.groups.iter());
    }
}

#[derive(Default)]
pub(super) struct MemberChainGroup<'a, 'b> {
    members: ChainMembers<'a, 'b>,

    /// Stores the formatted result of this group.
    ///
    /// Manual implementation of `Memoized` to only memorizing the formatted result
    /// if [MemberChainGroup::will_break] is called but not otherwise.
    formatted: RefCell<Option<FormatElement<'a>>>,

    needs_empty_line: Cell<bool>,
}

impl<'a, 'b> MemberChainGroup<'a, 'b> {
    pub(super) fn into_members(self) -> ChainMembers<'a, 'b> {
        self.members
    }

    /// Returns the chain members of the group.
    pub(super) fn members(&self) -> &[ChainMember<'a, 'b>] {
        &self.members
    }

    /// Extends the members of this group with the passed in members
    pub(super) fn extend_members(
        &mut self,
        members: impl IntoIterator<Item = ChainMember<'a, 'b>>,
    ) {
        self.members.extend(members);
    }

    pub(super) fn inspect(&self, tail: bool, f: &mut JsFormatter<'_, 'a>) {
        let mut cell = self.formatted.borrow_mut();
        if cell.is_none() {
            let interned = f.intern(&FormatMemberChainGroup { group: self });

            if tail {
                self.set_needs_empty_line(self.needs_empty_line_before(f));
            }

            *cell = interned;
        }
    }

    /// Tests if the formatted result of this group results in a [break](FormatElements::will_break).
    pub(super) fn will_break(&self, _f: &JsFormatter<'_, 'a>) -> bool {
        let cell = self.formatted.borrow_mut();
        if let Some(formatted) = cell.as_ref() {
            formatted.will_break()
        } else {
            unreachable!("Ensure that `inspect` is called before this method")
        }
    }

    pub(crate) fn set_needs_empty_line(&self, v: bool) {
        self.needs_empty_line.set(v);
    }

    pub(crate) fn needs_empty_line(&self) -> bool {
        self.needs_empty_line.get()
    }

    fn needs_empty_line_before(&self, f: &JsFormatter) -> bool {
        let Some(ChainMember::StaticMember(expression)) = self.members.first() else {
            return false;
        };

        // Prettier doesn't preserve blank lines after a terminal call like `fn()`
        // whose callee is not part of the chain: `fn()\n\n.bar()` becomes `fn().bar()`.
        // <https://github.com/prettier/prettier/blob/812a4d0071270f61a7aa549d625b618be7e09d71/src/language-js/print/member-chain.js#L101-L121>
        if let Expression::CallExpression(call) = expression.object().as_ref()
            && !call.callee.is_member_expression()
            && !call.callee.is_call_expression()
        {
            return false;
        }

        let source = f.source_text();

        // `A \n\n/* comment . */ . / comment . */ B`
        //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        // Check whether has more than 1 continuous new lines before the operator (`.`)
        let start = expression.object().span().end;
        let mut end = expression.property().span().start;

        // We should found the real `end` position that is before comments and it is not after the operator `.`
        if let Some(printed_comment) = f
            .comments()
            .printed_comments()
            .iter()
            .rev()
            .take_while(|c| start <= c.span.start && c.span.end < end)
            .last()
        {
            end = printed_comment.span.start;
        } else if let Some(first_comment) =
            f.comments().comments_before_character(start, b'.').first()
        {
            end = first_comment.span.start;
        }

        // Count the number of continuous new lines
        for (idx, &b) in source.bytes_range(start, end).iter().enumerate() {
            #[expect(clippy::cast_possible_truncation)]
            if matches!(b, b'\n' | b'\r') && source.lines_after(start + idx as u32) > 1 {
                return true;
            }
        }

        false
    }
}

impl<'a, 'b> From<ChainMembers<'a, 'b>> for MemberChainGroup<'a, 'b> {
    fn from(entries: ChainMembers<'a, 'b>) -> Self {
        Self { members: entries, formatted: RefCell::new(None), needs_empty_line: Cell::new(false) }
    }
}

impl std::fmt::Debug for MemberChainGroup<'_, '_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("MemberChainGroup").field(&self.members).finish()
    }
}

impl<'a> Format<'a, JsFormatContext<'a>> for MemberChainGroup<'a, '_> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        if let Some(formatted) = self.formatted.borrow().as_ref() {
            return f.write_element(formatted.clone());
        }

        FormatMemberChainGroup { group: self }.fmt(f);
    }
}

pub struct FormatMemberChainGroup<'a, 'b, 'g> {
    group: &'g MemberChainGroup<'a, 'b>,
}

impl<'a> Format<'a, JsFormatContext<'a>> for FormatMemberChainGroup<'a, '_, '_> {
    fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
        f.join().entries(self.group.members.iter());
    }
}
