/*
 * SPDX-FileCopyrightText: 2026 KOKA99CAB
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

package net.koka99cab.portals.portal;

import java.util.Optional;
import java.util.function.Predicate;

import net.koka99cab.portals.block.OrePortalBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;

public record PortalFrame(Direction.Axis axis, BlockPos origin) {
	public static final int INTERIOR_WIDTH = 2;
	public static final int INTERIOR_HEIGHT = 3;
	public static final int OUTER_WIDTH = INTERIOR_WIDTH + 2;
	public static final int OUTER_HEIGHT = INTERIOR_HEIGHT + 2;

	public static Optional<PortalFrame> findForIgnition(LevelReader level, BlockPos interiorPos, Predicate<BlockState> framePredicate) {
		return find(level, interiorPos, framePredicate, state -> state.isAir() || state.canBeReplaced());
	}

	public static Optional<PortalFrame> findExisting(LevelReader level, BlockPos portalPos, Direction.Axis axis, Block frameBlock, Block portalBlock) {
		return findExisting(level, portalPos, axis, state -> state.is(frameBlock), portalBlock);
	}

	public static Optional<PortalFrame> findExisting(LevelReader level, BlockPos portalPos, Direction.Axis axis, Predicate<BlockState> framePredicate, Block portalBlock) {
		return find(level, portalPos, axis, framePredicate, state -> state.is(portalBlock));
	}

	public void fill(Level level, BlockState portalState) {
		BlockState orientedState = portalState.setValue(OrePortalBlock.AXIS, axis);

		for (int horizontal = 1; horizontal <= INTERIOR_WIDTH; horizontal++) {
			for (int vertical = 1; vertical <= INTERIOR_HEIGHT; vertical++) {
				level.setBlock(at(horizontal, vertical), orientedState, Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE);
			}
		}
	}

	public BlockPos center() {
		return at(1, 1);
	}

	private static Optional<PortalFrame> find(LevelReader level, BlockPos interiorPos, Predicate<BlockState> framePredicate, Predicate<BlockState> interiorPredicate) {
		Optional<PortalFrame> xFrame = find(level, interiorPos, Direction.Axis.X, framePredicate, interiorPredicate);

		if (xFrame.isPresent()) {
			return xFrame;
		}

		return find(level, interiorPos, Direction.Axis.Z, framePredicate, interiorPredicate);
	}

	private static Optional<PortalFrame> find(LevelReader level, BlockPos interiorPos, Direction.Axis axis, Predicate<BlockState> framePredicate, Predicate<BlockState> interiorPredicate) {
		for (int horizontalOffset = 1; horizontalOffset <= INTERIOR_WIDTH; horizontalOffset++) {
			for (int verticalOffset = 1; verticalOffset <= INTERIOR_HEIGHT; verticalOffset++) {
				BlockPos origin = offset(interiorPos, axis, -horizontalOffset, -verticalOffset);
				PortalFrame frame = new PortalFrame(axis, origin);

				if (frame.isValid(level, framePredicate, interiorPredicate)) {
					return Optional.of(frame);
				}
			}
		}

		return Optional.empty();
	}

	private boolean isValid(LevelReader level, Predicate<BlockState> framePredicate, Predicate<BlockState> interiorPredicate) {
		for (int horizontal = 0; horizontal < OUTER_WIDTH; horizontal++) {
			for (int vertical = 0; vertical < OUTER_HEIGHT; vertical++) {
				BlockState state = level.getBlockState(at(horizontal, vertical));
				boolean border = horizontal == 0 || horizontal == OUTER_WIDTH - 1 || vertical == 0 || vertical == OUTER_HEIGHT - 1;

				if (border) {
					if (!framePredicate.test(state)) {
						return false;
					}
				} else if (!interiorPredicate.test(state)) {
					return false;
				}
			}
		}

		return true;
	}

	private BlockPos at(int horizontal, int vertical) {
		return offset(origin, axis, horizontal, vertical);
	}

	private static BlockPos offset(BlockPos pos, Direction.Axis axis, int horizontal, int vertical) {
		return switch (axis) {
			case X -> pos.east(horizontal).above(vertical);
			case Z -> pos.south(horizontal).above(vertical);
			default -> throw new IllegalArgumentException("Portal frame axis must be horizontal");
		};
	}
}
