#include"Assemble_Base.h"
#include"Nedelec3D_Util.h"
#include"PBC_Util.h"
#include"SBC_NormalUtil.h"
#include"../function/BF.h"
#include"../function/Gauss.h"
#include"../common/define.h"
#include"../common/util.h"
#include"../material/Material_Base.h"
#include"../phy/Phy_Base.h"
#include"../parser/mpParser.h"

#include<cmath>
#include<complex>
#include<string>
#include<vector>
#include<algorithm>

using namespace Eigen;

// SBC 边界组装：按第三类边界条件 Robin 形式 K += γ，b += q
//
// 散射场 SBC（教材）:  dE_sc/dn + ik₀ E_sc = 0  →  γ = ik₀，q = 0
//   BELE 算例 (SBCType=0):  E_s → Ae(γ) 进 A；E_b 的 ABC 切向项 + IBP 面项 → Assemble_BELE() 进 b
//   Robin q=0：SBC 上不组装 Be(E_inc)
//
// Nedelec 边元上面上的 γ 不能写成单个 K_nn+=γ，需面积分。
// 代码中 Ae(i,j) 来自 assembly_out.m，是 γ=ik₀√ε 在 Nedelec 基上的离散：
//   A_ij += ik₀√ε ∫ N_i·(n×(n×N_j)) dS
// 这与标量 K+=γ 是同一 Robin 项在矢量一阶基下的写法，不是体域 Se/Te。
//
// Be 仅用于 SBCType=1 显式入射（已知 q = Einc），不用于 BELE。

namespace {

Vector3d crossNormal(const Vector3d& normal, const Vector3d& a)
{
	return normal.cross(a.cross(normal));
}

Vector3cd crossNormal(const Vector3d& normal, const Vector3cd& a)
{
	return normal.cross(a.cross(normal));
}

double getScalarEps(int domainOfTet, MaterialLib* matLib)
{
	return matLib->GetEpsr(domainOfTet)(0, 0).real();
}

bool getFaceEdgeIndices(Mesh_3D* mesh, int triIdx, int outEdges[3])
{
	Vector3i conn;
	mesh->GetCoonOfTri(triIdx, conn);
	const int numTet = conn(0);
	const int numFace = conn(1) + 1;

	if (numFace == 1)
	{
		outEdges[0] = mesh->GetEdgeOfTet(numTet, 0);
		outEdges[1] = mesh->GetEdgeOfTet(numTet, 1);
		outEdges[2] = mesh->GetEdgeOfTet(numTet, 3);
	}
	else if (numFace == 2)
	{
		outEdges[0] = mesh->GetEdgeOfTet(numTet, 0);
		outEdges[1] = mesh->GetEdgeOfTet(numTet, 2);
		outEdges[2] = mesh->GetEdgeOfTet(numTet, 4);
	}
	else if (numFace == 3)
	{
		outEdges[0] = mesh->GetEdgeOfTet(numTet, 1);
		outEdges[1] = mesh->GetEdgeOfTet(numTet, 2);
		outEdges[2] = mesh->GetEdgeOfTet(numTet, 5);
	}
	else if (numFace == 4)
	{
		outEdges[0] = mesh->GetEdgeOfTet(numTet, 3);
		outEdges[1] = mesh->GetEdgeOfTet(numTet, 4);
		outEdges[2] = mesh->GetEdgeOfTet(numTet, 5);
	}
	else
		return false;
	return true;
}

void getEdgeGeometry(Mesh_3D* mesh, int edgeId, Vector3d& midpoint, Vector3d& edgeVec)
{
	const int v0 = mesh->GetEdge(edgeId, 0);
	const int v1 = mesh->GetEdge(edgeId, 1);
	Vector3d p0, p1;
	mesh->GetVertex(v0, p0);
	mesh->GetVertex(v1, p1);
	edgeVec = p1 - p0;
	midpoint = (p0 + p1) * 0.5;
}

Vector3cd evalE0AtPoint(
	mup::ParserX& parser,
	mup::Value& xx, mup::Value& yy, mup::Value& zz,
	const std::string& E0x, const std::string& E0y, const std::string& E0z,
	double x, double y, double z)
{
	xx = x;
	yy = y;
	zz = z;
	parser.SetExpr(_T(E0x.c_str()));
	mup::Value vx = parser.Eval();
	parser.SetExpr(_T(E0y.c_str()));
	mup::Value vy = parser.Eval();
	parser.SetExpr(_T(E0z.c_str()));
	mup::Value vz = parser.Eval();
	return Vector3cd(
		std::complex<double>(vx.GetFloat(), vx.GetImag()),
		std::complex<double>(vy.GetFloat(), vy.GetImag()),
		std::complex<double>(vz.GetFloat(), vz.GetImag()));
}

Vector3cd parseEincConstant(
	mup::ParserX& parser,
	mup::Value& xx, mup::Value& yy, mup::Value& zz,
	const std::string& Eincx, const std::string& Eincy, const std::string& Eincz)
{
	return evalE0AtPoint(parser, xx, yy, zz, Eincx, Eincy, Eincz, 0.0, 0.0, 0.0);
}

void applyDirichletBCReal(
	Eigen::SparseMatrix<double, Eigen::RowMajor>& A,
	Eigen::VectorXd& B,
	const Eigen::VectorXi& BCIndex,
	const Eigen::VectorXcd& BCValue)
{
	for (int i = 0; i < BCValue.rows(); i++)
	{
		Eigen::VectorXd tempValue = Eigen::VectorXd::Zero(A.rows());
		tempValue(BCIndex(i)) = 1.0;
		B = B - A * tempValue * BCValue(i).real();
	}

	Eigen::SparseMatrix<double, Eigen::RowMajor> P(A.rows(), A.rows());
	std::vector<Eigen::Triplet<double>> tempTriple;
	int num = 0;
	for (int i = 0; i < A.rows(); i++)
	{
		if (num < BCIndex.rows() && i == BCIndex(num))
			num++;
		else
			tempTriple.emplace_back(i, i, 1.0);
	}
	P.setFromTriplets(tempTriple.begin(), tempTriple.end());
	A = P * A * P;

	tempTriple.clear();
	for (int i = 0; i < BCIndex.rows(); i++)
	{
		tempTriple.emplace_back(BCIndex(i), BCIndex(i), 1.0);
		B(BCIndex(i)) = BCValue(i).real();
	}
	P.setZero();
	P.setFromTriplets(tempTriple.begin(), tempTriple.end());
	A = A + P;
	A.makeCompressed();
}

void applyDirichletBCComplex(
	Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& A,
	Eigen::VectorXcd& B,
	const Eigen::VectorXi& BCIndex,
	const Eigen::VectorXcd& BCValue)
{
	for (int i = 0; i < BCValue.rows(); i++)
	{
		Eigen::VectorXcd tempValue = Eigen::VectorXcd::Zero(A.rows());
		tempValue(BCIndex(i)) = 1.0;
		B = B - A * tempValue * BCValue(i);
	}

	Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> P(A.rows(), A.rows());
	std::vector<Eigen::Triplet<std::complex<double>>> tempTriple;
	int num = 0;
	for (int i = 0; i < A.rows(); i++)
	{
		if (num < BCIndex.rows() && i == BCIndex(num))
			num++;
		else
			tempTriple.emplace_back(i, i, 1.0);
	}
	P.setFromTriplets(tempTriple.begin(), tempTriple.end());
	A = P * A * P;

	tempTriple.clear();
	for (int i = 0; i < BCIndex.rows(); i++)
	{
		tempTriple.emplace_back(BCIndex(i), BCIndex(i), 1.0);
		B(BCIndex(i)) = BCValue(i);
	}
	P.setZero();
	P.setFromTriplets(tempTriple.begin(), tempTriple.end());
	A = A + P;
	A.makeCompressed();
}

void eliminateDirichletDofsComplex(
	Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>& A,
	Eigen::VectorXcd& B,
	const Eigen::VectorXi& elimIndices,
	std::vector<int>& freeIndicesOut)
{
	const int n = static_cast<int>(A.rows());
	std::vector<char> elim(static_cast<size_t>(n), 0);
	for (int i = 0; i < elimIndices.rows(); i++)
	{
		const int idx = elimIndices(i);
		if (idx >= 0 && idx < n)
			elim[static_cast<size_t>(idx)] = 1;
	}

	freeIndicesOut.clear();
	freeIndicesOut.reserve(static_cast<size_t>(n));
	for (int i = 0; i < n; i++)
		if (!elim[static_cast<size_t>(i)])
			freeIndicesOut.push_back(i);

	const int nf = static_cast<int>(freeIndicesOut.size());
	if (nf == n)
		return;

	std::vector<int> oldToNew(static_cast<size_t>(n), -1);
	for (int i = 0; i < nf; i++)
		oldToNew[static_cast<size_t>(freeIndicesOut[static_cast<size_t>(i)])] = i;

	std::vector<Eigen::Triplet<std::complex<double>>> trips;
	trips.reserve(static_cast<size_t>(A.nonZeros()));
	for (int col = 0; col < A.outerSize(); col++)
	{
		for (Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor>::InnerIterator it(A, col); it; ++it)
		{
			const int nr = oldToNew[static_cast<size_t>(it.row())];
			const int nc = oldToNew[static_cast<size_t>(it.col())];
			if (nr >= 0 && nc >= 0)
				trips.emplace_back(nr, nc, it.value());
		}
	}

	Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> Anew(nf, nf);
	Anew.setFromTriplets(trips.begin(), trips.end());
	A = Anew;

	Eigen::VectorXcd Bnew(nf);
	for (int i = 0; i < nf; i++)
		Bnew(i) = B(freeIndicesOut[static_cast<size_t>(i)]);
	B = Bnew;
	A.makeCompressed();
}

void physicalPointOnFace(
	double u, double v,
	const Vector3d& x2, const Vector3d& y2, const Vector3d& z2,
	const Vector3d& x3, const Vector3d& y3, const Vector3d& z3,
	double& px, double& py, double& pz)
{
	const double w = 1.0 - u - v;
	const double u2 = x2(0) * u + x2(1) * v + x2(2) * w;
	const double v2 = y2(0) * u + y2(1) * v + y2(2) * w;
	const double w2 = z2(0) * u + z2(1) * v + z2(2) * w;
	px = x3(0) * u2 + x3(1) * v2 + x3(2) * w2;
	py = y3(0) * u2 + y3(1) * v2 + y3(2) * w2;
	pz = z3(0) * u2 + z3(1) * v2 + z3(2) * w2;
}

void assembleSBCFace(
	Mesh_3D* mesh,
	MaterialLib* matLib,
	std::vector<Triplet<std::complex<double>>>& tripleA,
	VectorXcd& B,
	double k0,
	double* u, double* v, double* wght, int nbrGP,
	BF& bfN,
	bool isInc,
	const Vector3cd& Einc,
	int triIdx,
	int elementOrder)
{
	const int nBfFace = (elementOrder == 2) ? 8 : 3;
	const int domain = mesh->GetDomainOfTri(triIdx);

	Vector3i conn;
	mesh->GetCoonOfTri(triIdx, conn);
	const int numTet = conn(0);
	const int numFace = conn(1) + 1;

	double xv[4], yv[4], zv[4];
	for (int i = 0; i < 4; i++)
	{
		Vector3d vtx;
		mesh->GetVertex(mesh->GetTet(numTet, i), vtx);
		xv[i] = vtx(0); yv[i] = vtx(1); zv[i] = vtx(2);
	}

	Vector3d x2, y2, z2;
	Vector3d x3, y3, z3;
	std::vector<int> bfIndex(nBfFace);
	std::vector<int> mappingIndex(nBfFace);

	if (elementOrder == 2)
	{
		if (numFace == 1)
		{
			x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 1;
			x3 << xv[0], xv[1], xv[2]; y3 << yv[0], yv[1], yv[2]; z3 << zv[0], zv[1], zv[2];
		}
		else if (numFace == 2)
		{
			x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 0;
			x3 << xv[0], xv[1], xv[3]; y3 << yv[0], yv[1], yv[3]; z3 << zv[0], zv[1], zv[3];
		}
		else if (numFace == 3)
		{
			x2 << 1, 0, 0; y2 << 0, 0, 0; z2 << 0, 1, 0;
			x3 << xv[0], xv[2], xv[3]; y3 << yv[0], yv[2], yv[3]; z3 << zv[0], zv[2], zv[3];
		}
		else if (numFace == 4)
		{
			x2 << 0, 0, 0; y2 << 1, 0, 0; z2 << 0, 1, 0;
			x3 << xv[1], xv[2], xv[3]; y3 << yv[1], yv[2], yv[3]; z3 << zv[1], zv[2], zv[3];
		}
		else
			return;

		int bfIdx[8], mapIdx[8];
		Nedelec3D::sbcFaceSecondOrderBfIndex(numFace, bfIdx);
		Nedelec3D::buildSbcSecondOrderDofMap(mesh, numTet, numFace, mapIdx);
		for (int i = 0; i < 8; i++)
		{
			bfIndex[static_cast<size_t>(i)] = bfIdx[i];
			mappingIndex[static_cast<size_t>(i)] = mapIdx[i];
		}
	}
	else if (numFace == 1)
	{
		x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 1;
		x3 << xv[0], xv[1], xv[2]; y3 << yv[0], yv[1], yv[2]; z3 << zv[0], zv[1], zv[2];
		bfIndex[0] = 1; bfIndex[1] = 2; bfIndex[2] = 4;
		mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 0);
		mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 1);
		mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 3);
	}
	else if (numFace == 2)
	{
		x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 0;
		x3 << xv[0], xv[1], xv[3]; y3 << yv[0], yv[1], yv[3]; z3 << zv[0], zv[1], zv[3];
		bfIndex[0] = 1; bfIndex[1] = 3; bfIndex[2] = 5;
		mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 0);
		mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 2);
		mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 4);
	}
	else if (numFace == 3)
	{
		x2 << 1, 0, 0; y2 << 0, 0, 0; z2 << 0, 1, 0;
		x3 << xv[0], xv[2], xv[3]; y3 << yv[0], yv[2], yv[3]; z3 << zv[0], zv[2], zv[3];
		bfIndex[0] = 2; bfIndex[1] = 3; bfIndex[2] = 6;
		mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 1);
		mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 2);
		mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 5);
	}
	else if (numFace == 4)
	{
		x2 << 0, 0, 0; y2 << 1, 0, 0; z2 << 0, 1, 0;
		x3 << xv[1], xv[2], xv[3]; y3 << yv[1], yv[2], yv[3]; z3 << zv[1], zv[2], zv[3];
		bfIndex[0] = 4; bfIndex[1] = 5; bfIndex[2] = 6;
		mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 3);
		mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 4);
		mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 5);
	}
	else
	{
		return;
	}

	Matrix3d Jac;
	Jac(0, 0) = xv[0] - xv[3]; Jac(0, 1) = yv[0] - yv[3]; Jac(0, 2) = zv[0] - zv[3];
	Jac(1, 0) = xv[1] - xv[3]; Jac(1, 1) = yv[1] - yv[3]; Jac(1, 2) = zv[1] - zv[3];
	Jac(2, 0) = xv[2] - xv[3]; Jac(2, 1) = yv[2] - yv[3]; Jac(2, 2) = zv[2] - zv[3];
	const double detJ = Jac.determinant();
	if (std::abs(detJ) < 1e-30)
		return;
	const Matrix3d InvJac = Jac.inverse();

	const double a = std::sqrt((x3(0) - x3(1)) * (x3(0) - x3(1)) + (y3(0) - y3(1)) * (y3(0) - y3(1)) + (z3(0) - z3(1)) * (z3(0) - z3(1)));
	const double b = std::sqrt((x3(0) - x3(2)) * (x3(0) - x3(2)) + (y3(0) - y3(2)) * (y3(0) - y3(2)) + (z3(0) - z3(2)) * (z3(0) - z3(2)));
	const double c = std::sqrt((x3(1) - x3(2)) * (x3(1) - x3(2)) + (y3(1) - y3(2)) * (y3(1) - y3(2)) + (z3(1) - z3(2)) * (z3(1) - z3(2)));
	double heron = (a + b + c) * (a + b - c) * (a - b + c) * (b + c - a);
	if (heron < 0.0) heron = 0.0;
	const double integCoe = 0.25 * std::sqrt(heron);

	Vector3d meshNorm;
	mesh->GetNormOfFace(domain, meshNorm);
	const Vector3d faceP0(x3(0), y3(0), z3(0));
	const Vector3d faceP1(x3(1), y3(1), z3(1));
	const Vector3d faceP2(x3(2), y3(2), z3(2));
	const Vector3d normal = OpticsFEM::computeScatterSBCNormal(
		isInc, domain, xv, yv, faceP0, faceP1, faceP2, &meshNorm);

	std::vector<std::vector<Vector3d>> E(static_cast<size_t>(nbrGP), std::vector<Vector3d>(nBfFace));
	for (int gp = 0; gp < nbrGP; gp++)
	{
		const double wgp = 1.0 - u[gp] - v[gp];
		const double u2 = x2(0) * u[gp] + x2(1) * v[gp] + x2(2) * wgp;
		const double v2 = y2(0) * u[gp] + y2(1) * v[gp] + y2(2) * wgp;
		const double w2 = z2(0) * u[gp] + z2(1) * v[gp] + z2(2) * wgp;
		for (int j = 0; j < nBfFace; j++)
		{
			bfN.GetValueBF(bfIndex[static_cast<size_t>(j)], u2, v2, w2, E[static_cast<size_t>(gp)][static_cast<size_t>(j)]);
			E[static_cast<size_t>(gp)][static_cast<size_t>(j)] = InvJac * E[static_cast<size_t>(gp)][static_cast<size_t>(j)];
		}
	}

	const int tetDomain = mesh->GetDomainOfTet(numTet);
	const double eps = getScalarEps(tetDomain, matLib);
	const std::complex<double> nn = std::sqrt(std::complex<double>(eps, 0.0));
	const std::complex<double> iUnit(0.0, 1.0);

	MatrixXcd Ae = MatrixXcd::Zero(nBfFace, nBfFace);
	for (int i = 0; i < nBfFace; i++)
	{
		for (int j = 0; j < nBfFace; j++)
		{
			for (int gp = 0; gp < nbrGP; gp++)
			{
				const Vector3d tEj = crossNormal(normal, E[static_cast<size_t>(gp)][static_cast<size_t>(j)]);
				Ae(i, j) += iUnit * k0 * nn * integCoe * wght[gp]
					* E[static_cast<size_t>(gp)][static_cast<size_t>(i)].dot(tEj) * 2.0;
			}
		}
	}

	std::vector<std::complex<double>> Be(static_cast<size_t>(nBfFace), 0.0);
	if (isInc)
	{
		const Vector3cd tEinc = crossNormal(normal, Einc);
		for (int i = 0; i < nBfFace; i++)
		{
			for (int gp = 0; gp < nbrGP; gp++)
			{
				const std::complex<double> dotE =
					E[static_cast<size_t>(gp)][static_cast<size_t>(i)](0) * tEinc(0)
					+ E[static_cast<size_t>(gp)][static_cast<size_t>(i)](1) * tEinc(1)
					+ E[static_cast<size_t>(gp)][static_cast<size_t>(i)](2) * tEinc(2);
				Be[static_cast<size_t>(i)] -= iUnit * k0 * nn * 2.0 * integCoe * wght[gp] * dotE * 2.0;
			}
		}
	}

	for (int i = 0; i < nBfFace; i++)
	{
		for (int j = 0; j < nBfFace; j++)
			tripleA.emplace_back(mappingIndex[static_cast<size_t>(i)], mappingIndex[static_cast<size_t>(j)], Ae(i, j));
		if (isInc)
			B(mappingIndex[static_cast<size_t>(i)]) += Be[static_cast<size_t>(i)];
	}
}

void assembleSBCByType(
	Mesh_3D* mesh,
	MaterialLib* matLib,
	Phy_WaveOpticsModel* phy,
	std::vector<Triplet<std::complex<double>>>& tripleA,
	VectorXcd& B,
	double k0,
	int sbcTypeFilter,
	bool isInc,
	const Vector3cd& Einc)
{
	const int elementOrder = phy->GetElementOrder();
	const int gaussTriOrder = Nedelec3D::gaussOrderTri(elementOrder);
	const int bfOrder = Nedelec3D::bfOrderParam(elementOrder);

	Gauss gauss;
	const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, gaussTriOrder);
	double* u = new double[nbrGP];
	double* v = new double[nbrGP];
	double* w = new double[nbrGP];
	double* wght = new double[nbrGP];
	gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght);

	BF bfN;
	bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder);
	const int nbrSBC = phy->GetNbrSBC();
	for (int s = 0; s < nbrSBC; s++)
	{
		if (phy->GetSBCType(s) != sbcTypeFilter)
			continue;

		VectorXi triIndices;
		mesh->GetTriIndicesOfDomain(phy->GetSBCDomain(s), triIndices);
		for (int n = 0; n < triIndices.size(); n++)
		{
			assembleSBCFace(mesh, matLib, tripleA, B, k0, u, v, wght, nbrGP, bfN,
				isInc, Einc, triIndices(n), elementOrder);
		}
	}

	delete[] u;
	delete[] v;
	delete[] w;
	delete[] wght;
}

void assembleRobinOnDomains(
	Mesh_3D* mesh,
	MaterialLib* matLib,
	std::vector<Triplet<std::complex<double>>>& tripleA,
	VectorXcd& B,
	double k0,
	const Eigen::VectorXi& domains)
{
	Gauss gauss;
	const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2);
	double* u = new double[nbrGP];
	double* v = new double[nbrGP];
	double* w = new double[nbrGP];
	double* wght = new double[nbrGP];
	gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght);

	BF bfN;
	bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC);
	for (int d = 0; d < domains.rows(); d++)
	{
		VectorXi triIndices;
		mesh->GetTriIndicesOfDomain(domains(d), triIndices);
		for (int n = 0; n < triIndices.size(); n++)
		{
			assembleSBCFace(mesh, matLib, tripleA, B, k0, u, v, wght, nbrGP, bfN,
				false, Vector3cd::Zero(), triIndices(n), 1);
		}
	}

	delete[] u;
	delete[] v;
	delete[] w;
	delete[] wght;
}

void collectBoundaryEdgesOnDomains(
	Mesh_3D* mesh,
	const Eigen::VectorXi& domains,
	std::vector<int>& edgeIds)
{
	for (int i = 0; i < domains.rows(); i++)
	{
		Eigen::VectorXi triIndices;
		mesh->GetTriIndicesOfDomain(domains(i), triIndices);
		for (int t = 0; t < triIndices.size(); t++)
		{
			int faceEdges[3];
			if (!getFaceEdgeIndices(mesh, triIndices(t), faceEdges))
				continue;
			for (int n = 0; n < 3; n++)
				edgeIds.push_back(faceEdges[n]);
		}
	}
	std::sort(edgeIds.begin(), edgeIds.end());
	edgeIds.erase(std::unique(edgeIds.begin(), edgeIds.end()), edgeIds.end());
}

void collectBoundaryFacesOnDomains(
	Mesh_3D* mesh,
	const Eigen::VectorXi& domains,
	std::vector<int>& faceIds)
{
	for (int i = 0; i < domains.rows(); i++)
	{
		Eigen::VectorXi triIndices;
		mesh->GetTriIndicesOfDomain(domains(i), triIndices);
		for (int t = 0; t < triIndices.size(); t++)
		{
			Eigen::Vector3i conn;
			mesh->GetCoonOfTri(triIndices(t), conn);
			faceIds.push_back(mesh->GetFaceOfTet(conn(0), conn(1)));
		}
	}
	std::sort(faceIds.begin(), faceIds.end());
	faceIds.erase(std::unique(faceIds.begin(), faceIds.end()), faceIds.end());
}

void collectPecDofIndices(
	Mesh_3D* mesh,
	const Eigen::VectorXi& pecDomains,
	int elementOrder,
	std::vector<int>& dofIndices)
{
	std::vector<int> pecEdges;
	collectBoundaryEdgesOnDomains(mesh, pecDomains, pecEdges);
	const int nE = mesh->GetNbrEdge();
	const int nF = mesh->GetNbrFace();
	for (int edgeId : pecEdges)
	{
		dofIndices.push_back(Nedelec3D::edgeGlobalDof(edgeId, 0, nE));
		if (elementOrder == 2)
			dofIndices.push_back(Nedelec3D::edgeGlobalDof(edgeId, 1, nE));
	}
	if (elementOrder == 2)
	{
		std::vector<int> pecFaces;
		collectBoundaryFacesOnDomains(mesh, pecDomains, pecFaces);
		for (int faceId : pecFaces)
		{
			dofIndices.push_back(Nedelec3D::faceGlobalDof(faceId, 0, nE, nF));
			dofIndices.push_back(Nedelec3D::faceGlobalDof(faceId, 1, nE, nF));
		}
	}
	std::sort(dofIndices.begin(), dofIndices.end());
	dofIndices.erase(std::unique(dofIndices.begin(), dofIndices.end()), dofIndices.end());
}

} // namespace

void OpticsFEM_3D_Scatter::Assemble_SBC()
{
	const double k0 = 2.0 * Pi / _mSolver->GetLda0();

	// SBCType=0: γ=ik₀，q=0 → 只修正 A
	assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex,
		k0, 0, false, Vector3cd::Zero());

	Vector3cd Einc = Vector3cd::Zero();
	for (int s = 0; s < _mPhy->GetNbrSBC(); s++)
	{
		if (_mPhy->GetSBCType(s) != 1)
			continue;
		std::string sx, sy, sz;
		_mPhy->GetEinc(s, sx, sy, sz);
		mup::ParserX parser(mup::pckALL_COMPLEX);
		parser.EnableAutoCreateVar(true);
		mup::Value xx(0.0), yy(0.0), zz(0.0);
		parser.DefineVar(_T("x"), mup::Variable(&xx));
		parser.DefineVar(_T("y"), mup::Variable(&yy));
		parser.DefineVar(_T("z"), mup::Variable(&zz));
		Einc = parseEincConstant(parser, xx, yy, zz, sx, sy, sz);
		break;
	}
	assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex,
		k0, 1, true, Einc);
}

void OpticsFEM_3D_Scatter::Assemble_PEC_ELE()
{
	const int elementOrder = _mPhy->GetElementOrder();
	const int nbrPEC = _mPhy->GetNbrPEC();
	const int nbrELE = _mPhy->GetNbrElE();
	if (nbrPEC == 0 && nbrELE == 0)
		return;

	if (nbrELE > 0 && elementOrder == 2)
	{
		std::cerr << "[OpticsFEM] ElementOrder=2: ELE uses edge DOFs (×2) + zero face DOFs "
			<< "(midpoint tangential projection)." << std::endl;
	}

	std::vector<int> pecDofs;
	if (nbrPEC > 0)
	{
		Eigen::VectorXi PECDomain = Eigen::VectorXi::Zero(nbrPEC);
		for (int i = 0; i < nbrPEC; i++)
			PECDomain(i) = _mPhy->GetPECDomain(i) + 1;
		collectPecDofIndices(_mMesh, PECDomain, elementOrder, pecDofs);
	}

	Eigen::VectorXi triIndexOfELE, triNumOfELE;
	if (nbrELE > 0)
	{
		Eigen::VectorXi ELEDomain = Eigen::VectorXi::Zero(nbrELE);
		Eigen::VectorXi indexNum = Eigen::VectorXi::Zero(nbrELE);
		for (int i = 0; i < nbrELE; i++)
		{
			ELEDomain(i) = _mPhy->GetELEDomain(i);
			indexNum(i) = i;
		}
		_mMesh->GetTriIndexOfDomain2(ELEDomain, indexNum, triIndexOfELE, triNumOfELE);
	}

	const int dofsPerEleFace = (elementOrder == 2) ? 8 : 3;
	const int nbrELEEdges = (nbrELE > 0) ? static_cast<int>(triIndexOfELE.size()) * dofsPerEleFace : 0;
	const int nbrPECDofs = (nbrPEC > 0) ? static_cast<int>(pecDofs.size()) : 0;
	const int nbrBC = nbrPECDofs + nbrELEEdges;
	if (nbrBC == 0)
		return;

	Eigen::VectorXi BCIndex = Eigen::VectorXi::Zero(nbrBC);
	Eigen::VectorXcd BCValue = Eigen::VectorXcd::Zero(nbrBC);

	mup::ParserX parser(mup::pckALL_COMPLEX);
	parser.EnableAutoCreateVar(true);
	mup::Value xx, yy, zz;
	parser.DefineVar(_T("x"), mup::Variable(&xx));
	parser.DefineVar(_T("y"), mup::Variable(&yy));
	parser.DefineVar(_T("z"), mup::Variable(&zz));

	int bcOffset = 0;
	for (int m = 0; m < triIndexOfELE.size(); m++)
	{
		int faceEdges[3];
		if (!getFaceEdgeIndices(_mMesh, triIndexOfELE(m), faceEdges))
			continue;

		std::string E0x, E0y, E0z;
		_mPhy->GetE0(E0x, E0y, E0z, triNumOfELE(m));

		std::string ExFunc = E0x, EyFunc = E0y, EzFunc = E0z;
		if (_mPhy->GetNbrBELE() > 0)
		{
			std::string Ebx, Eby, Ebz;
			_mPhy->GetEb(Ebx, Eby, Ebz);
			ExFunc = E0x + "-" + Ebx;
			EyFunc = E0y + "-" + Eby;
			EzFunc = E0z + "-" + Ebz;
		}

		const int nE = _mMesh->GetNbrEdge();
		if (elementOrder == 2)
		{
			Eigen::Vector3i conn;
			_mMesh->GetCoonOfTri(triIndexOfELE(m), conn);
			int mapIdx[8];
			Nedelec3D::buildSbcSecondOrderDofMap(_mMesh, conn(0), conn(1) + 1, mapIdx);
			for (int n = 0; n < 3; n++)
			{
				Vector3d midpoint, edgeVec;
				getEdgeGeometry(_mMesh, faceEdges[n], midpoint, edgeVec);
				xx = midpoint(0);
				yy = midpoint(1);
				zz = midpoint(2);
				const Vector3cd E0 = evalE0AtPoint(parser, xx, yy, zz, ExFunc, EyFunc, EzFunc,
					midpoint(0), midpoint(1), midpoint(2));
				const std::complex<double> edgeVal = E0.dot(edgeVec);
				BCIndex(bcOffset + n) = mapIdx[n];
				BCValue(bcOffset + n) = edgeVal;
				BCIndex(bcOffset + 3 + n) = mapIdx[3 + n];
				BCValue(bcOffset + 3 + n) = edgeVal;
			}
			BCIndex(bcOffset + 6) = mapIdx[6];
			BCValue(bcOffset + 6) = 0.0;
			BCIndex(bcOffset + 7) = mapIdx[7];
			BCValue(bcOffset + 7) = 0.0;
			bcOffset += 8;
			(void)nE;
		}
		else
		{
			for (int n = 0; n < 3; n++)
			{
				Vector3d midpoint, edgeVec;
				getEdgeGeometry(_mMesh, faceEdges[n], midpoint, edgeVec);
				xx = midpoint(0);
				yy = midpoint(1);
				zz = midpoint(2);
				const Vector3cd E0 = evalE0AtPoint(parser, xx, yy, zz, ExFunc, EyFunc, EzFunc,
					midpoint(0), midpoint(1), midpoint(2));
				BCIndex(bcOffset + n) = faceEdges[n];
				BCValue(bcOffset + n) = E0.dot(edgeVec);
			}
			bcOffset += 3;
		}
	}

	for (int i = 0; i < nbrPECDofs; i++)
	{
		BCIndex(bcOffset + i) = pecDofs[static_cast<size_t>(i)];
		BCValue(bcOffset + i) = 0.0;
	}

	Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(nbrBC, 0, nbrBC - 1);
	QuickSort(BCIndex, tempIndex, 0, nbrBC - 1);
	Unique(BCIndex, tempIndex);
	Eigen::VectorXcd tempValue = Eigen::VectorXcd::Zero(BCIndex.rows());
	for (int i = 0; i < BCIndex.rows(); i++)
		tempValue(i) = BCValue(tempIndex(i));
	BCValue = tempValue;

	// Port + PEC-only: match MATLAB FemMatrixAssembly (eliminate PEC DOFs before solve).
	// Do NOT use applyDirichletBC here — it breaks the augmented port system.
	if (!_mIsReal && _mPhy->HasNumericPort() && nbrELE == 0 && nbrPECDofs > 0)
	{
		eliminateDirichletDofsComplex(_mA_complex, _mB_complex, BCIndex, _mFreeDofIndices);
		return;
	}

	if (_mIsReal)
		applyDirichletBCReal(_mA_real, _mB_real, BCIndex, BCValue);
	else
		applyDirichletBCComplex(_mA_complex, _mB_complex, BCIndex, BCValue);
}

void OpticsFEM_3D_Scatter::Assemble_PBC()
{
	if (_mPhy->GetNbrPBCGroups() <= 0)
		return;

	const int elementOrder = _mPhy->GetElementOrder();
	const int dof = Nedelec3D::globalDofCount(_mMesh, elementOrder);
	assemblePbcProjectionMatrix(_mMesh, _mPhy, elementOrder, dof, _mIsReal, _mP_real, _mP_complex);
}
