use std::collections::HashSet;

use crate::{
    block::{
        BlockBehaviour, BlockFuture, BlockIsReplacing, CanPlaceAtArgs, CanUpdateAtArgs,
        GetStateForNeighborUpdateArgs, OnPlaceArgs, UseWithItemArgs, registry::BlockActionResult,
    },
    entity::{EntityBase, player::Player},
};
use pumpkin_data::{
    Block, BlockDirection, BlockStateId, FacingExt,
    block_properties::{BlockProperties, VineLikeProperties},
    item::Item,
};
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::world::{BlockAccessor, BlockFlags};

#[pumpkin_block("minecraft:vine")]
pub struct VineBlock;

impl BlockBehaviour for VineBlock {
    fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
        Box::pin(async move {
            if let BlockIsReplacing::Itself(state_id) = args.replacing {
                let (Some(direction), _) = get_accurate_direction(
                    args.world,
                    args.position,
                    Some(args.player),
                    args.direction,
                    true,
                ) else {
                    return Block::AIR.default_state.id;
                };
                let mut props = VineLikeProperties::from_state_id(state_id, args.block);
                vine_direction_mapper(direction, &mut props);
                return props.to_state_id(args.block);
            }
            let (Some(direction), _) = get_accurate_direction(
                args.world,
                args.position,
                Some(args.player),
                args.direction,
                false,
            ) else {
                return Block::AIR.default_state.id;
            };
            let mut props = VineLikeProperties::default(args.block);
            vine_direction_mapper(direction, &mut props);
            props.to_state_id(args.block)
        })
    }
    fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
        can_place_vine_at(
            args.block_accessor,
            args.position,
            args.direction,
            args.player,
            false,
        )
    }
    fn get_state_for_neighbor_update<'a>(
        &'a self,
        args: GetStateForNeighborUpdateArgs<'a>,
    ) -> BlockFuture<'a, BlockStateId> {
        Box::pin(async move {
            let old_props = VineLikeProperties::from_state_id(args.state_id, args.block);
            let old_directions = get_vine_block_directions(old_props);
            let mut new_directions = old_directions.clone();
            for old_dir in old_directions {
                let support_block = args
                    .world
                    .get_block(&args.position.offset(old_dir.to_offset()));
                if !supports_vine(support_block)
                    && !is_top_block_full_vine(args.world, args.position)
                {
                    new_directions.remove(&old_dir);
                }
            }
            if new_directions.is_empty() {
                return Block::AIR.default_state.id;
            }
            let mut new_props = VineLikeProperties::default(args.block);

            for new_dir in new_directions {
                vine_direction_mapper(new_dir, &mut new_props);
            }

            new_props.to_state_id(args.block)
        })
    }
    fn can_update_at(&self, args: CanUpdateAtArgs<'_>) -> bool {
        get_accurate_direction(
            args.world,
            args.position,
            Some(args.player),
            args.direction,
            true,
        )
        .0
        .is_some()
    }
    fn use_with_item<'a>(
        &'a self,
        args: UseWithItemArgs<'a>,
    ) -> BlockFuture<'a, BlockActionResult> {
        Box::pin(async move {
            let state = args.world.get_block_state(args.position);
            let mut props = VineLikeProperties::from_state_id(state.id, args.block);

            let item = args.item_stack.item;

            if item.id != Item::VINE.id {
                return BlockActionResult::Pass;
            }
            let (Some(accurate_dir), _) = get_accurate_direction(
                args.world.as_ref(),
                args.position,
                Some(args.player),
                BlockDirection::Down,
                true,
            ) else {
                return BlockActionResult::Fail;
            };
            vine_direction_mapper(accurate_dir, &mut props);

            args.world
                .set_block_state(
                    args.position,
                    props.to_state_id(args.block),
                    BlockFlags::NOTIFY_ALL,
                )
                .await;
            BlockActionResult::Consume
        })
    }
}
pub fn get_nearest_looking_directions(
    player: &Player,
    replace_clicked: bool,
    clicked_face: BlockDirection,
) -> [BlockDirection; 6] {
    let mut directions: [BlockDirection; 6] = {
        let fs = player.get_entity().get_entity_facing_order();
        [
            fs[0].to_block_direction(),
            fs[1].to_block_direction(),
            fs[2].to_block_direction(),
            fs[3].to_block_direction(),
            fs[4].to_block_direction(),
            fs[5].to_block_direction(),
        ]
    };

    if !replace_clicked {
        let target = clicked_face.opposite();

        let mut index = 0;

        while index < directions.len() && directions[index] != target {
            index += 1;
        }

        if index > 0 {
            directions.copy_within(0..index, 1);
            directions[0] = target;
        }
    }
    directions
}
fn can_place_vine_at(
    block_accessor: &dyn BlockAccessor,
    block_pos: &BlockPos,
    click_direction_wrapper: Option<BlockDirection>,
    player_wrapper: Option<&Player>,
    replacing: bool,
) -> bool {
    let Some(click_direction) = click_direction_wrapper else {
        return false;
    };
    let (direction, _) = get_accurate_direction(
        block_accessor,
        block_pos,
        player_wrapper,
        click_direction,
        replacing,
    );
    let Some(direction) = direction else {
        return false;
    };

    let support_pos = block_pos.offset(direction.to_offset());
    let (support_block, _support_block_state) = block_accessor.get_block_and_state(&support_pos);
    if !supports_vine(support_block) && !is_top_block_full_vine(block_accessor, block_pos) {
        return false;
    }
    true
}
const fn supports_vine(support_block: &Block) -> bool {
    if support_block.default_state.is_full_cube() {
        return true;
    }
    false
}
//returns (accurate direction, boolean)
// true if this direction is for hanging vine
// false if it is not
fn get_accurate_direction(
    block_accessor: &dyn BlockAccessor,
    block_pos: &BlockPos,
    player_wrapper: Option<&Player>,
    click_direction: BlockDirection,
    replacing: bool,
) -> (Option<BlockDirection>, bool) {
    let clicked_block = block_accessor.get_block(&block_pos.offset(click_direction.to_offset()));
    if !replacing && clicked_block == &Block::VINE && click_direction != BlockDirection::Up {
        return (None, false);
    }

    if click_direction != BlockDirection::Down && supports_vine(clicked_block) {
        return (Some(click_direction), false);
    }
    let (replacing_block, replacing_block_state) = block_accessor.get_block_and_state(block_pos);
    let already_active_directions = if replacing_block == &Block::VINE {
        let props = VineLikeProperties::from_state_id(replacing_block_state.id, replacing_block);
        get_vine_block_directions(props)
    } else {
        HashSet::new()
    };
    if let Some(player) = player_wrapper {
        let mut up = false;
        for dir in get_nearest_looking_directions(player, replacing, click_direction) {
            if dir != BlockDirection::Down && !already_active_directions.contains(&dir) {
                let support_pos = block_pos.offset(dir.to_offset());
                let (support_block, _support_block_state) =
                    block_accessor.get_block_and_state(&support_pos);
                if !supports_vine(support_block) {
                    //handler for hanging vine
                    if is_top_block_full_vine(block_accessor, block_pos) {
                        if dir == BlockDirection::Up {
                            continue;
                        }
                        return (Some(dir), true);
                    }
                    continue;
                }
                if dir == BlockDirection::Up && !replacing {
                    up = true;
                    continue;
                }

                return (Some(dir), false);
            }
        }
        if up {
            return (Some(BlockDirection::Up), false);
        }
    }
    (None, false)
}
fn is_top_block_full_vine(block_accessor: &dyn BlockAccessor, block_pos: &BlockPos) -> bool {
    let (top_block, top_block_state) = block_accessor.get_block_and_state(&block_pos.up());
    if top_block != &Block::VINE {
        return false;
    }
    let props = VineLikeProperties::from_state_id(top_block_state.id, top_block);
    props.up && props.west && props.east && props.north && props.south
}
fn get_vine_block_directions(props: VineLikeProperties) -> HashSet<BlockDirection> {
    let mut set = HashSet::new();
    if props.north {
        set.insert(BlockDirection::North);
    }
    if props.south {
        set.insert(BlockDirection::South);
    }
    if props.east {
        set.insert(BlockDirection::East);
    }
    if props.west {
        set.insert(BlockDirection::West);
    }
    if props.up {
        set.insert(BlockDirection::Up);
    }
    set
}
const fn vine_direction_mapper(direction: BlockDirection, props: &mut VineLikeProperties) {
    match direction {
        BlockDirection::Down => (),
        BlockDirection::Up => props.up = true,
        BlockDirection::North => props.north = true,
        BlockDirection::South => props.south = true,
        BlockDirection::West => props.west = true,
        BlockDirection::East => props.east = true,
    }
}
