PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB`Mini Shell

HOME


Mini Shell 1.0
DIR:/home/htlwork.com/www/dev/pricelow/vendor/firebear/importexport/Model/Import/
Upload File :
Current File : /home/htlwork.com/www/dev/pricelow/vendor/firebear/importexport/Model/Import/Product.php
<?php
declare(strict_types=1);
/**
 * @copyright: Copyright © 2017 Firebear Studio. All rights reserved.
 * @author   : Firebear Studio <fbeardev@gmail.com>
 */

namespace Firebear\ImportExport\Model\Import;

use Firebear\ImportExport\Model\JobRepository;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Firebear\ImportExport\Ui\Component\Listing\Column\Import\Source\StockStatus\Options as StockStatusOptions;
use Magento\Framework\DB\Select;
use function array_diff;
use function array_keys;
use function array_map;
use function array_merge;
use function array_values;
use Exception;
use function explode;
use Firebear\ImportExport\Api\UrlKeyManagerInterface;
use Firebear\ImportExport\Helper\Additional;
use Firebear\ImportExport\Helper\Data as FirebearImportExportData;
use Firebear\ImportExport\Model\Cache\Type\ImportProduct as ImportProductCache;
use Firebear\ImportExport\Model\Export\RowCustomizer\ProductVideo;
use Firebear\ImportExport\Model\Import;
use Firebear\ImportExport\Model\Import\Product\CategoryProcessor;
use Firebear\ImportExport\Model\Import\Product\Image as ImportImage;
use Firebear\ImportExport\Model\Import\Product\ImageProcessor as ImportImageProcessor;
use Firebear\ImportExport\Model\Import\Product\Integration\IntegrationInterface;
use Firebear\ImportExport\Model\Import\Product\Integration\MageArrayMarketplace;
use Firebear\ImportExport\Model\Import\Product\Integration\WebkulMarketplace;
use Firebear\ImportExport\Model\Import\Product\MediaVideoGallery;
use Firebear\ImportExport\Model\Import\Product\OptionFactory;
use Firebear\ImportExport\Model\Import\Product\Price\Rule\ConditionFactory as ConditionFactoryAlias;
use Firebear\ImportExport\Model\Import\Product\Type\Downloadable;
use Firebear\ImportExport\Model\Job;
use Firebear\ImportExport\Model\ResourceModel\Job\CollectionFactory;
use Firebear\ImportExport\Model\Source\Import\Config;
use Firebear\ImportExport\Model\Source\Type\AbstractType;
use Firebear\ImportExport\Model\Translation\Translator;
use Firebear\ImportExport\Traits\Import\Entity as ImportTrait;
use Firebear\ImportExport\Ui\Component\Listing\Column\Entity\Import\Attributes\SystemOptions;
use Firebear\ImportExport\Ui\Component\Listing\Column\Import\Source\Configurable\Type\Options as TypeOptions;
use Firebear\ImportExport\Api\Data\SeparatorFormatterInterface;
use function implode;
use function in_array;
use function interface_exists;
use InvalidArgumentException;
use function is_array;
use function is_int;
use Magento\Bundle\Model\Product\Price as BundlePrice;
use Magento\BundleImportExport\Model\Import\Product\Type\Bundle;
use Magento\Catalog\Api\Data\CategoryLinkInterface;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Helper\Data as CatalogHelperData;
use Magento\Catalog\Helper\Product as CatalogHelperProduct;
use Magento\Catalog\Model\CategoryLinkRepository;
use Magento\Catalog\Model\Config as CatalogConfig;
use Magento\Catalog\Model\Product\Action;
use Magento\Catalog\Model\Product\ActionFactory;
use Magento\Catalog\Model\Product\Attribute\Backend\Sku;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\Product\Link as ProductLink;
use Magento\Catalog\Model\Product\Media\ConfigInterface;
use Magento\Catalog\Model\Product\Url;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Catalog\Model\ResourceModel\Product\LinkFactory;
use Magento\CatalogImportExport\Model\Import\Product as MagentoProduct;
use Magento\CatalogImportExport\Model\Import\Product\ImageTypeProcessor;
use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor;
use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface;
use Magento\CatalogImportExport\Model\Import\Product\SkuProcessor;
use Magento\CatalogImportExport\Model\Import\Product\StoreResolver;
use Magento\CatalogImportExport\Model\Import\Product\TaxClassProcessor;
use Magento\CatalogImportExport\Model\Import\Product\Type\Factory;
use Magento\CatalogImportExport\Model\Import\Product\Validator;
use Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModel;
use Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory;
use Magento\CatalogImportExport\Model\Import\Proxy\ProductFactory;
use Magento\CatalogImportExport\Model\StockItemImporterInterface;
use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\CatalogInventory\Model\ResourceModel\Stock\Item;
use Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory;
use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface;
use Magento\Customer\Model\GroupFactory;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
use Magento\Eav\Model\EntityFactory;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory as AttributeGroupCollectionFactory;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory;
use Magento\Framework\App\CacheInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Adapter\ConnectionException;
use Magento\Framework\DB\Adapter\DeadlockException;
use Magento\Framework\DB\Adapter\LockWaitException;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Filesystem;
use Magento\Framework\Indexer\IndexerRegistry;
use Magento\Framework\Intl\DateTimeFactory;
use Magento\Framework\MessageQueue\PublisherInterface;
use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor;
use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface;
use Magento\Framework\Module\Manager;
use Magento\Framework\Stdlib\DateTime;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\ImportExport\Model\Import\Config as ImportConfig;
use Magento\ImportExport\Model\Import\Entity\AbstractEntity;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Model\WebsiteFactory;
use Magento\Swatches\Helper\Data;
use Magento\Swatches\Helper\Media;
use Magento\Swatches\Model\ResourceModel\Swatch\CollectionFactory as SwatchCollectionFactory;
use Magento\Swatches\Model\Swatch;
use Magento\Tax\Model\ClassModel;
use Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory as TaxClassCollectionFactory;
use function mb_strtolower;
use function strlen;
use function strtolower;
use function substr;
use Symfony\Component\Console\Output\ConsoleOutput;
use function version_compare;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable as TypeConfigurable;
use Laminas\Validator\Regex;

/**
 * Import entity product model
 *
 * @api
 *
 * @SuppressWarnings(PHPMD.TooManyFields)
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 * @since 100.0.2
 */
class Product extends MagentoProduct
{
    use ImportTrait;

    /**
     * Admin website id
     */
    const ADMIN_WEBSITE_ID = 0;

    /**
     * Default website id
     */
    const DEFAULT_WEBSITE_ID = 1;

    /**
     * Default global multiple value separator
     */
    private const DEFAULT_GLOBAL_MULTIPLE_VALUE_SEPARATOR = ',';

    /**
     * Used when create new attributes in column name
     */
    const ATTRIBUTE_SET_GROUP = 'attribute_set_group';
    /**
     * Attribute sets column name
     */
    const ATTRIBUTE_SET_COLUMN = 'attribute_set';

    /**
     * Maximum number of database retries
     */
    const MAX_DB_RETRIES = 5;

    protected $mediaProcessor;

    protected $classTaxNames = [];

    /**
     * @var ProductMetadataInterface
     */
    public $productMetadata;
    public $onlyUpdate = 0;
    protected $onlyAdd = false;
    public static $addFields = [
        'manage_stock',
        'use_config_manage_stock',
        'qty',
        'out_of_stock_qty',
        'min_qty',
        'use_config_min_qty',
        'min_sale_qty',
        'use_config_min_sale_qty',
        'max_sale_qty',
        'use_config_max_sale_qty',
        'is_qty_decimal',
        'backorders',
        'use_config_backorders',
        'notify_stock_qty',
        'use_config_notify_stock_qty',
        'enable_qty_increments',
        'use_config_enable_qty_inc',
        'qty_increments',
        'use_config_qty_increments',
        'is_in_stock',
        'low_stock_date',
        'stock_status_changed_auto',
        'is_decimal_divided',
        'has_options',
        'tax_class_name',
        self::COL_STORE_VIEW_CODE,
        'attribute_set_code',
        'configurable_variations',
        'configurable_variation_labels',
        'associated_skus',
        'base_image_label',
        'additional_images',
        'additional_image_labels',
        'small_image_label',
        'thumbnail_image_label',
        'swatch_image',
        'swatch_image_label',
        'remove_images',
        ProductVideo::VIDEO_URL_COLUMN,
        WebkulMarketplace::VENDOR_ID,
        WebkulMarketplace::COL_UNASSIGN_SELLER,
        MageArrayMarketplace::VENDOR_ID,
        MageArrayMarketplace::MAGE_PRICE_COMPARE,
        'additional_attributes',
        'custom_options'
    ];

    public static $specialAttributes = [
        self::COL_STORE,
        self::COL_ATTR_SET,
        self::COL_TYPE,
        self::COL_CATEGORY,
        self::COL_CATEGORY . '_position',
        'product_websites',
        self::COL_PRODUCT_WEBSITES,
        '_tier_price_website',
        '_tier_price_customer_group',
        '_tier_price_qty',
        '_tier_price_price',
        '_related_sku',
        '_related_position',
        '_crosssell_sku',
        '_crosssell_position',
        '_upsell_sku',
        '_upsell_position',
        '_custom_option_store',
        '_custom_option_type',
        '_custom_option_title',
        '_custom_option_is_required',
        '_custom_option_price',
        '_custom_option_sku',
        '_custom_option_max_characters',
        '_custom_option_sort_order',
        '_custom_option_file_extension',
        '_custom_option_image_size_x',
        '_custom_option_image_size_y',
        '_custom_option_row_title',
        '_custom_option_row_price',
        '_custom_option_row_sku',
        '_custom_option_row_sort',
        '_media_attribute_id',
        self::COL_MEDIA_IMAGE,
        '_media_label',
        '_media_position',
        '_media_is_disabled',
        '_tier_price_value_type',
        'product_online',
        'msi_source_code',
        'update_attribute_set',
    ];

    /**
     * @var UploaderFactory
     */
    protected $_uploaderFactory;

    /**
     * @var Product\CategoryProcessor
     */
    protected $categoryProcessor;

    /**
     * @var FirebearImportExportData
     */
    protected $helper;
    /**
     * @var Additional
     */
    protected $additional;
    /**
     * @var AbstractType
     */
    protected $sourceType;
    /**
     * @var AttributeFactory
     */
    protected $attributeFactory;
    /**
     * @var EntityFactory
     */
    protected $eavEntityFactory;
    /**
     * @var AttributeGroupCollectionFactory
     */
    protected $groupCollectionFactory;
    /**
     * @var array
     */
    protected $_attributeSetGroupCache;
    /**
     * @var CatalogHelperProduct
     */
    protected $productHelper;

    protected $_debugMode;
    /**
     * @var Config
     */
    protected $fireImportConfig;
    /**
     * @var ProductRepositoryInterface
     */
    protected $productRepository;

    /**
     * @var ProductCollectionFactory
     */
    protected $collectionFactory;

    /**
     * @var Data
     */
    protected $swatchesHelperData;

    /**
     * Helper to move image from tmp to catalog
     *
     * @var Media
     */
    protected $swatchHelperMedia;

    /**
     * @var SwatchCollectionFactory
     */
    protected $swatchCollectionFactory;

    /**
     * @var ConfigInterface
     */
    protected $mediaConfig;
    /**
     * @var GroupFactory
     */
    protected $groupFactory;
    /**
     * @var WebsiteFactory
     */
    protected $websiteFactory;

    /**
     * @var TaxClassCollectionFactory
     */
    protected $collectionTaxFactory;
    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;
    protected $notValidedSku = [];
    protected $productLinkData = [];
    protected $productLink;
    protected $priceRuleConditionFactory;
    protected $platform;
    /** @var Manager */
    protected $manager;

    /**
     * @var string
     */
    protected $currentSku = '';

    /**
     * @var UrlKeyManagerInterface
     */
    protected $urlKeyManager;

    /**
     * @var array
     */
    public $urlPatternData = [
        'cache' => 0,
        'allowed_functions' => [
            'rand',
            'mt_rand'
        ]
    ];
    /**
     * @var ActionFactory
     */
    protected $productActionFactory;

    private $cachedSwatchOptions = [];
    private $importCollection;
    private $_isRowCategoryMapped;
    private $lastSku;

    /**
     * Product entity identifier field
     *
     * @var string
     */
    private $productEntityIdentifierField;

    /**
     * Product entity link field
     *
     * @var string
     */
    private $productEntityLinkField;

    /** @var Translator */
    protected $translator;

    /**
     *
     * @var CategoryLinkRepository
     */
    protected $categoryLinkRepository;

    /**
     * Stock Item Importer
     *
     * @var StockItemImporterInterface
     */
    private $stockItemImporter;

    protected $categoryProductPosition = [];

    /**
     * @var ProductCollectionFactory
     */
    private $productCollectionFactory;

    /** @var array */
    protected $integrations;

    /**
     * @var PublisherInterface
     */
    protected $publisher;

    /**
     * @var ImportImage
     */
    protected $importImage;

    /**
     * @var ImportImageProcessor
     */
    protected $importImageProcessor;

    /**
     * @var CacheInterface
     */
    protected $cache;

    /**
     * @var array
     */
    protected $originalImportRows = [];

    /**
     * @var array
     */
    protected $getProductIdByRowId = [];

    /**
     * @var array
     */
    protected $adminAttributeValue = [];

    /**
     * @var SourceManager
     */
    protected $sourceManager;

    /**
     * @var Product\ConfigurationVariations
     */
    protected $configVariations;

    /**
     * @var SeparatorFormatterInterface
     */
    private $separatorFormatter;
    /**
     * @var JobRepository
     */
    protected $jobRepository;

    /**
     * @var \Firebear\ImportExport\Api\Data\ImportInterface[]
     */
    protected $jobsCache = [];

    private $storeIds = [];

    /**
     * @var \Magento\Framework\App\RequestInterface
     */
    protected $request;

    /**
     * Product constructor.
     * @param Context $context
     * @param FirebearImportExportData $helper
     * @param Additional $additional
     * @param ManagerInterface $eventManager
     * @param StockRegistryInterface $stockRegistry
     * @param StockConfigurationInterface $stockConfiguration
     * @param StockStateProviderInterface $stockStateProvider
     * @param CatalogHelperData $catalogData
     * @param ImportConfig $importConfig
     * @param Config $fireImportConfig
     * @param ResourceModelFactory $resourceFactory
     * @param OptionFactory $optionFactory
     * @param AttributeSetCollectionFactory $setColFactory
     * @param Factory $productTypeFactory
     * @param LinkFactory $linkFactory
     * @param ProductFactory $proxyProdFactory
     * @param Filesystem $filesystem
     * @param ItemFactory $stockResItemFac
     * @param TimezoneInterface $localeDate
     * @param DateTime $dateTime
     * @param IndexerRegistry $indexerRegistry
     * @param StoreResolver $storeResolver
     * @param SkuProcessor $skuProcessor
     * @param Validator $validator
     * @param ObjectRelationProcessor $objectRelationProcessor
     * @param TransactionManagerInterface $transactionManager
     * @param TaxClassProcessor $taxClassProcessor
     * @param ScopeConfigInterface $scopeConfig
     * @param Url $productUrl
     * @param AttributeFactory $attributeFactory
     * @param EntityFactory $eavEntityFactory
     * @param AttributeGroupCollectionFactory $groupCollectionFactory
     * @param CatalogHelperProduct $productHelper
     * @param ProductMetadataInterface $productMetadata
     * @param ProductRepositoryInterface $productRepository
     * @param ProductCollectionFactory $collectionFactory
     * @param GroupFactory $groupFactory
     * @param WebsiteFactory $websiteFactory
     * @param CategoryProcessor $categoryProcessor
     * @param UploaderFactory $uploaderFactory
     * @param TaxClassCollectionFactory $collectionTaxFactory
     * @param StoreManagerInterface $storeManager
     * @param CollectionFactory $importCollectionFactory
     * @param ConditionFactoryAlias $priceRuleConditionFactory
     * @param Data $swatchesHelperData
     * @param Media $swatchHelperMedia
     * @param SwatchCollectionFactory $swatchCollectionFactory
     * @param ConfigInterface $mediaConfig
     * @param Manager $moduleManager
     * @param ProductLink $productLink
     * @param UrlKeyManagerInterface $urlKeyManager
     * @param CategoryLinkRepository $categoryLinkRepository
     * @param ActionFactory $productActionFactory
     * @param Translator $translator
     * @param Product\ConfigurationVariations $configurationVariations
     * @param SeparatorFormatterInterface $separatorFormatter
     * @param JobRepository $jobRepository
     * @param array $data
     * @param array $integrations
     * @param array $dateAttrCodes
     * @param CatalogConfig|null $catalogConfig
     * @param DateTimeFactory|null $dateTimeFactory
     * @throws LocalizedException
     * @throws Exception
     */
    public function __construct(
        Context $context,
        ImportImage $importImage,
        ImportImageProcessor $importImageProcessor,
        FirebearImportExportData $helper,
        CacheInterface $cache,
        Additional $additional,
        ManagerInterface $eventManager,
        StockRegistryInterface $stockRegistry,
        StockConfigurationInterface $stockConfiguration,
        StockStateProviderInterface $stockStateProvider,
        CatalogHelperData $catalogData,
        ImportConfig $importConfig,
        Config $fireImportConfig,
        ResourceModelFactory $resourceFactory,
        Product\OptionFactory $optionFactory,
        AttributeSetCollectionFactory $setColFactory,
        Factory $productTypeFactory,
        LinkFactory $linkFactory,
        ProductFactory $proxyProdFactory,
        Filesystem $filesystem,
        ItemFactory $stockResItemFac,
        TimezoneInterface $localeDate,
        DateTime $dateTime,
        IndexerRegistry $indexerRegistry,
        StoreResolver $storeResolver,
        SkuProcessor $skuProcessor,
        Validator $validator,
        ObjectRelationProcessor $objectRelationProcessor,
        TransactionManagerInterface $transactionManager,
        TaxClassProcessor $taxClassProcessor,
        ScopeConfigInterface $scopeConfig,
        Url $productUrl,
        AttributeFactory $attributeFactory,
        EntityFactory $eavEntityFactory,
        AttributeGroupCollectionFactory $groupCollectionFactory,
        CatalogHelperProduct $productHelper,
        ProductMetadataInterface $productMetadata,
        ProductRepositoryInterface $productRepository,
        ProductCollectionFactory $collectionFactory,
        GroupFactory $groupFactory,
        WebsiteFactory $websiteFactory,
        Product\CategoryProcessor $categoryProcessor,
        UploaderFactory $uploaderFactory,
        TaxClassCollectionFactory $collectionTaxFactory,
        StoreManagerInterface $storeManager,
        CollectionFactory $importCollectionFactory,
        Product\Price\Rule\ConditionFactory $priceRuleConditionFactory,
        Data $swatchesHelperData,
        Media $swatchHelperMedia,
        SwatchCollectionFactory $swatchCollectionFactory,
        ConfigInterface $mediaConfig,
        Manager $moduleManager,
        ProductLink $productLink,
        UrlKeyManagerInterface $urlKeyManager,
        CategoryLinkRepository $categoryLinkRepository,
        ActionFactory $productActionFactory,
        Translator $translator,
        SourceManager $sourceManager,
        Product\ConfigurationVariations $configurationVariations,
        SeparatorFormatterInterface $separatorFormatter,
        JobRepository $jobRepository,
        \Magento\Framework\App\RequestInterface $request,
        array $data = [],
        array $integrations = [],
        array $dateAttrCodes = [],
        CatalogConfig $catalogConfig = null,
        DateTimeFactory $dateTimeFactory = null
    ) {
        $this->output = $context->getOutput();
        $this->sourceManager = $sourceManager;
        $this->importImage = $importImage;
        $this->cache = $cache;
        $this->configVariations = $configurationVariations;
        $this->helper = $helper;
        $this->importImageProcessor = $importImageProcessor;
        $this->attributeFactory = $attributeFactory;
        $this->eavEntityFactory = $eavEntityFactory;
        $this->groupCollectionFactory = $groupCollectionFactory;
        $this->productHelper = $productHelper;
        $this->additional = $additional;
        $this->fireImportConfig = $fireImportConfig;
        $this->groupFactory = $groupFactory;
        $this->storeManager = $storeManager;
        $this->priceRuleConditionFactory = $priceRuleConditionFactory;
        $this->swatchesHelperData = $swatchesHelperData;
        $this->swatchHelperMedia = $swatchHelperMedia;
        $this->swatchCollectionFactory = $swatchCollectionFactory;
        $this->mediaConfig = $mediaConfig;
        $this->translator = $translator;
        $this->productLink = $productLink;
        $this->separatorFormatter = $separatorFormatter;

        if (class_exists(MediaGalleryProcessor::class)) {
            $this->mediaProcessor = ObjectManager::getInstance()
                ->get(MediaVideoGallery::class);
            $mediaProcessor = ObjectManager::getInstance()
                ->get(MediaGalleryProcessor::class);
        }

        if (interface_exists(StockItemImporterInterface::class)) {
            $this->stockItemImporter = ObjectManager::getInstance()
                ->get(\Firebear\ImportExport\Model\Import\StockItemImporterInterface::class);
        }
        if (class_exists(ImageTypeProcessor::class)) {
            $imageTypeProcessor = ObjectManager::getInstance()
                ->get(ImageTypeProcessor::class);
        }
        if (interface_exists(PublisherInterface::class)) {
            $this->publisher = ObjectManager::getInstance()
                ->get(PublisherInterface::class);
        }
        $this->setSerializer($context->getSerializer());
        $this->setLogger($context->getLogger());
        if (version_compare($productMetadata->getVersion(), '2.3.0', '>=')) {
            parent::__construct(
                $context->getJsonHelper(),
                $context->getImportExportData(),
                $context->getDataSourceModel(),
                $context->getConfig(),
                $context->getResource(),
                $context->getResourceHelper(),
                $context->getStringUtils(),
                $context->getErrorAggregator(),
                $eventManager,
                $stockRegistry,
                $stockConfiguration,
                $stockStateProvider,
                $catalogData,
                $importConfig,
                $resourceFactory,
                $optionFactory,
                $setColFactory,
                $productTypeFactory,
                $linkFactory,
                $proxyProdFactory,
                $uploaderFactory,
                $filesystem,
                $stockResItemFac,
                $localeDate,
                $dateTime,
                $context->getLogger(),
                $indexerRegistry,
                $storeResolver,
                $skuProcessor,
                $categoryProcessor,
                $validator,
                $objectRelationProcessor,
                $transactionManager,
                $taxClassProcessor,
                $scopeConfig,
                $productUrl,
                $data,
                $dateAttrCodes,
                $catalogConfig,
                $imageTypeProcessor,
                $mediaProcessor,
                $this->stockItemImporter,
                $dateTimeFactory,
                $productRepository
            );
        } elseif (version_compare($productMetadata->getVersion(), '2.2.3', '<=')) {
            parent::__construct(
                $context->getJsonHelper(),
                $context->getImportExportData(),
                $context->getDataSourceModel(),
                $context->getConfig(),
                $context->getResource(),
                $context->getResourceHelper(),
                $context->getStringUtils(),
                $context->getErrorAggregator(),
                $eventManager,
                $stockRegistry,
                $stockConfiguration,
                $stockStateProvider,
                $catalogData,
                $importConfig,
                $resourceFactory,
                $optionFactory,
                $setColFactory,
                $productTypeFactory,
                $linkFactory,
                $proxyProdFactory,
                $uploaderFactory,
                $filesystem,
                $stockResItemFac,
                $localeDate,
                $dateTime,
                $context->getLogger(),
                $indexerRegistry,
                $storeResolver,
                $skuProcessor,
                $categoryProcessor,
                $validator,
                $objectRelationProcessor,
                $transactionManager,
                $taxClassProcessor,
                $scopeConfig,
                $productUrl,
                $data,
                $dateAttrCodes,
                $catalogConfig
            );
        } else {
            parent::__construct(
                $context->getJsonHelper(),
                $context->getImportExportData(),
                $context->getDataSourceModel(),
                $context->getConfig(),
                $context->getResource(),
                $context->getResourceHelper(),
                $context->getStringUtils(),
                $context->getErrorAggregator(),
                $eventManager,
                $stockRegistry,
                $stockConfiguration,
                $stockStateProvider,
                $catalogData,
                $importConfig,
                $resourceFactory,
                $optionFactory,
                $setColFactory,
                $productTypeFactory,
                $linkFactory,
                $proxyProdFactory,
                $uploaderFactory,
                $filesystem,
                $stockResItemFac,
                $localeDate,
                $dateTime,
                $context->getLogger(),
                $indexerRegistry,
                $storeResolver,
                $skuProcessor,
                $categoryProcessor,
                $validator,
                $objectRelationProcessor,
                $transactionManager,
                $taxClassProcessor,
                $scopeConfig,
                $productUrl,
                $data,
                $dateAttrCodes,
                $catalogConfig,
                $mediaProcessor
            );
        }
        $this->_debugMode = $helper->getDebugMode();
        $this->productMetadata = $productMetadata;
        $this->productRepository = $productRepository;
        $this->collectionFactory = $collectionFactory;
        $this->websiteFactory = $websiteFactory;
        $this->collectionTaxFactory = $collectionTaxFactory;
        $this->importCollection = $importCollectionFactory;
        $this->_specialAttributes[] = '_tier_price_value_type';
        $this->_fieldsMap += [
            '_tier_price_website' => 'tier_price_website',
            '_tier_price_customer_group' => 'tier_price_customer_group',
            '_tier_price_qty' => 'tier_price_qty',
            '_tier_price_price' => 'tier_price_price',
            '_tier_price_value_type' => 'tier_price_value_type',
        ];
        $this->_isRowCategoryMapped = false;
        $this->manager = $moduleManager;
        $this->urlKeyManager = $urlKeyManager;
        $this->productActionFactory = $productActionFactory;
        $this->categoryLinkRepository = $categoryLinkRepository;
        $this->productCollectionFactory = $collectionFactory;
        foreach ($integrations as $integration) {
            if (!($integration instanceof IntegrationInterface)) {
                throw new LocalizedException(__(
                    'Integration class must be instance of "%interface"',
                    ['interface' => IntegrationInterface::class]
                ));
            }
        }
        $this->integrations = $integrations;
        $this->_fireConstruct();
        $this->jobRepository = $jobRepository;
        $this->request = $request;
    }

    /**
     * @return string
     * @throws Exception
     */
    public function _getProductEntityLinkField()
    {
        return $this->getProductEntityLinkField();
    }

    /**
     * @return $this
     * @throws Exception
     */
    public function _fireConstruct()
    {
        $this->urlKeyManager
            ->setEntity($this)
            ->initUrlKeys();
        return $this;
    }

    /**
     * Remove large objects
     */
    public function __destruct()
    {
        unset($this->_optionEntity);
    }

    /**
     * import product data
     *
     * @return bool
     * @throws Exception
     * @throws LocalizedException
     */
    public function importData()
    {
        $this->notValidedSku = [];
        if ($this->_parameters['behavior'] == Import::FIREBEAR_ONLY_UPDATE) {
            $this->onlyUpdate = 1;
            $this->_parameters['behavior'] = Import::BEHAVIOR_APPEND;
        } elseif ($this->_parameters['behavior'] == Import::FIREBEAR_ONLY_ADD) {
            $this->onlyAdd = true;
            $this->_parameters['behavior'] = Import::BEHAVIOR_APPEND;
        }
        $this->_validatedRows = null;

        if (Import::BEHAVIOR_REPLACE == $this->getBehavior()) {
            $this->_replaceFlag = true;
            $this->replaceProducts();
        } elseif (Import::BEHAVIOR_DELETE == $this->getBehavior()) {
            $this->_deleteProducts();
        } else {
            $this->saveProductsData();
        }
        $this->_eventManager->dispatch('catalog_product_import_finish_before', ['adapter' => $this]);

        return true;
    }

    /**
     * Delete products.
     *
     * @return $this
     * @throws Exception
     */
    protected function _deleteProducts()
    {
        $productEntityTable = $this->_resourceFactory->create()->getEntityTable();

        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            $idsToDelete = [];
            foreach ($bunch as $rowNum => $rowData) {
                $validate = $this->validateRow($rowData, $rowNum);
                $scopeStore = self::SCOPE_STORE == $this->getRowScope($rowData);
                $scopeDefault = self::SCOPE_DEFAULT == $this->getRowScope($rowData);
                if ($validate && ($scopeStore || $scopeDefault)) {
                    $oldSku = $this->getExistingSku($rowData[self::COL_SKU])['entity_id'];
                    if (!empty($oldSku)) {
                        $idsToDelete[] = $oldSku;
                    }
                }
            }
            if ($idsToDelete) {
                $this->countItemsDeleted += count($idsToDelete);
                $this->_processedEntitiesCount += count($idsToDelete);
                $this->transactionManager->start($this->_connection);
                try {
                    $this->objectRelationProcessor->delete(
                        $this->transactionManager,
                        $this->_connection,
                        $productEntityTable,
                        $this->_connection->quoteInto('entity_id IN (?)', $idsToDelete),
                        ['entity_id' => $idsToDelete]
                    );
                    $this->_eventManager->dispatch(
                        'catalog_product_import_bunch_delete_commit_before',
                        [
                            'adapter' => $this,
                            'bunch' => $bunch,
                            'ids_to_delete' => $idsToDelete,
                        ]
                    );
                    $this->transactionManager->commit();
                } catch (Exception $e) {
                    $this->transactionManager->rollBack();
                    throw $e;
                }
                $this->_eventManager->dispatch(
                    'catalog_product_import_bunch_delete_after',
                    ['adapter' => $this, 'bunch' => $bunch]
                );
            }
        }
        return $this;
    }

    /**
     * Get next bunch of validated rows.
     *
     * @return array|null
     */
    public function getNextBunch()
    {
        return $this->_dataSourceModel->getNextBunch();
    }

    /**
     * Replace imported products.
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function replaceProducts()
    {
        $this->deleteProductsForReplacement();
        $this->_oldSku = $this->skuProcessor->reloadOldSkus()->getOldSkus();
        $this->_validatedRows = null;
        $this->setParameters(
            array_merge(
                $this->getParameters(),
                ['behavior' => Import::BEHAVIOR_APPEND]
            )
        );
        $this->saveProductsData();

        return $this;
    }

    /**
     * find url_key duplicates
     *
     * @param array $bunchRows
     * @return array $bunchRows
     */
    protected function findUrlKeyDuplicates($bunchRows)
    {
        $urlKeys = [];
        foreach ($bunchRows as $rowNum => $rowData) {
            if (array_key_exists(self::COL_STORE_VIEW_CODE, $rowData)
                && $rowData[self::COL_STORE_VIEW_CODE] === null
            ) {
                $storeCode = 'admin';
            } else {
                $storeCode = $rowData[self::COL_STORE_VIEW_CODE] ?? 'default';
            }

            if (!isset($urlKeys[$storeCode])) {
                $urlKeys[$storeCode] = [];
            }
            $sku = $rowData[self::COL_SKU] ?? '';
            if (!$this->isUniqueUrlKeyInBunch($rowData, $urlKeys, $storeCode)) {
                unset($bunchRows[$rowNum]);
                $message = 'product with sku: %1 not imported because its url is not unique.';
                $this->addLogWriteln(__($message, $this->getCorrectSkuAsPerLength($rowData)), $this->output, 'info');
            } elseif (!empty($rowData[self::URL_KEY])) {
                $urlKeys[$storeCode][$rowData[self::URL_KEY]] = $sku;
            }
        }

        return $bunchRows;
    }

    /**
     * @param $rowData
     * @param $bunchUrlKeys
     * @param $storeCode
     * @return bool
     */
    protected function isUniqueUrlKeyInBunch($rowData, $bunchUrlKeys, $storeCode)
    {
        $sku = $rowData[self::COL_SKU] ?? '';
        if (isset($rowData[self::URL_KEY])) {
            $skuByUrlKey = $bunchUrlKeys[$storeCode][$rowData[self::URL_KEY]] ?? null;
            if (in_array($rowData[self::URL_KEY], $bunchUrlKeys[$storeCode]) &&
                (!empty($skuByUrlKey) && $skuByUrlKey !== $sku)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Save products data.
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function saveProductsData()
    {
        foreach ($this->_productTypeModels as $productTypeModel) {
            if ($productTypeModel instanceof Downloadable) {
                $productTypeModel->clearObject();
            }
        }
        $this->saveProducts();
        foreach ($this->_productTypeModels as $productTypeModel) {
            $productTypeModel->saveData();
        }

        $this->_saveLinks();
        $this->_saveStockItem();

        if ($this->_replaceFlag) {
            $this->getOptionEntity()->clearProductsSkuToId();
        }
        $this->getOptionEntity()->importData();
        $verbosity = false;
        if (!$this->helper->getProcessor()->inConsole) {
            $verbosity = ConsoleOutput::VERBOSITY_VERBOSE;
        }
        if (is_array($this->integrations)) {
            /**
             * @var $moduleKey
             * @var Import\Product\Integration\AbstractIntegration $integration
             */
            foreach ($this->integrations as $moduleKey => $integration) {
                if ($this->manager->isEnabled($moduleKey)) {
                    $integration->setLogger($this->getLogger());
                    $integration->setAdapter($this);
                    $integration->setDataSourceModel($this->_dataSourceModel);
                    $integration->importData($verbosity);
                }
            }
        }
        foreach ($this->productLinkData as $idConfigProduct => $typesLink) {
            $this->addProductLinks($idConfigProduct, $typesLink);
        }

        return $this;
    }

    /**
     * Save Stock Item.
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function _saveStockItem()
    {
        /** @var $stockResource Item */
        $stockResource = $this->_stockResItemFac->create();
        $entityTable = $stockResource->getMainTable();
        $toSetMsiSourceData = true;
        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            if ($this->sourceManager->isEnableMsi() && $this->sourceManager->getCoreFields($bunch)) {
                $toSetMsiSourceData = false;
            }
            $stockData = [];
            $msiSourceData = [];
            // Format bunch to stock data rows
            foreach ($bunch as $rowNum => $rowData) {
                if (!$this->isRowAllowedToImport($rowData, $rowNum)) {
                    continue;
                }
                if (isset($this->_parameters['increase_product_stock_by_qty'])
                    && $this->_parameters['increase_product_stock_by_qty'] == 1
                ) {
                    $rowData['qty'] = (int)($rowData['qty'] + $this->getProductStockQty($rowData[self::COL_SKU]));
                }

                if (!isset($rowData['qty']) && !isset($rowData['is_in_stock'])) {
                    continue;
                }

                $sku = $rowData[self::COL_SKU];
                $row = [];
                if ($this->skuProcessor->getNewSku($sku) !== null
                    && $this->checkIfStockAttributesExist($rowData)
                ) {
                    $row = $this->formatStockDataForRow($rowData);
                }
                if (isset($rowData['msi_source_code']) && !empty($rowData['msi_source_code']) && $toSetMsiSourceData) {
                    $msiSourceData[$sku] = $rowData['msi_source_code'];
                }
                if (!isset($stockData[$sku]) && !empty($row)) {
                    $stockData[$sku] = $row;
                }
            }

            // Insert rows
            if (!empty($stockData)) {
                if ($this->stockItemImporter instanceof StockItemImporterInterface
                    && version_compare($this->productMetadata->getVersion(), '2.3.0', '>=')
                ) {
                    try {
                        if ($toSetMsiSourceData) {
                            $this->stockItemImporter->setSourceData($msiSourceData);
                        }
                        $this->stockItemImporter->import($stockData);
                    } catch (Exception $exception) {
                        $this->addLogWriteln($exception->getMessage(), $this->getOutput(), 'info');
                        $this->getLogger()->debug($exception);
                    }
                } else {
                    $this->_connection->insertOnDuplicate($entityTable, array_values($stockData));
                }
            }
        }
        return $this;
    }

    /**
     * @param $rowData
     * @return bool
     */
    protected function checkIfStockAttributesExist($rowData)
    {
        foreach ($this->defaultStockData as $attribute => $value) {
            if (isset($rowData[$attribute])) {
                return true;
            }
        }
        return false;
    }

    /**
     * Format row data to DB compatible values
     *
     * @param array $rowData
     * @return array
     * @throws Exception
     */
    protected function formatStockDataForRow(array $rowData)
    {
        $sku = $rowData[self::COL_SKU];
        $row['product_id'] = $this->skuProcessor->getNewSku($sku)['entity_id'];
        $row['website_id'] = $this->stockConfiguration->getDefaultScopeId();
        $row['stock_id'] = $this->stockRegistry->getStock($row['website_id'])->getStockId();

        /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItemDo */
        $stockItemDo = $this->stockRegistry->getStockItem($row['product_id'], $row['website_id']);
        $existStockData = $stockItemDo->getData();

        $row = array_merge(
            $this->defaultStockData,
            array_intersect_key($existStockData, $this->defaultStockData),
            array_intersect_key($rowData, $this->defaultStockData),
            $row
        );

        /**
         * NULL value will cause mysql error: 1048 Column cannot be null.
         * Replace NULL with default value.
         */
        array_walk($row, function (&$value, $key) {
            $value = ($value === null && isset($this->defaultStockData[$key]))
                ? $this->defaultStockData[$key]
                : $value;
        });

        $typeId = $this->skuProcessor->getNewSku($sku)['type_id'];
        if ($this->stockConfiguration->isQty($typeId)) {
            if ($this->manager->isEnabled('Magento_CatalogMessageBus')) {
                if (isset($existStockData['qty'])) {
                    $row['qty'] = $existStockData['qty'];
                }
            }
            $stockItemDo->setData($row);
            $isBackOrderAllowed = !empty($row['backorders']) || $stockItemDo->getBackorders();

            if (!isset($rowData['is_in_stock']) && isset($rowData['qty'])) {
                $rowData['is_in_stock'] = $rowData['qty'] > 0 ? 1 : 0;
            }
            $row['is_in_stock'] = $rowData['is_in_stock']
                ?? $row['is_in_stock']
                ?? 0;

            if ($this->stockStateProvider->verifyNotification($stockItemDo)) {
                $row['low_stock_date'] = $this->dateTime->gmDate(
                    'Y-m-d H:i:s',
                    (new \DateTime())->getTimestamp()
                );
            }
            if (isset($this->_parameters['set_product_as_out_of_stock_if_qty_is_0'])
                && $this->_parameters['set_product_as_out_of_stock_if_qty_is_0'] == 1
                && !$row['qty']
            ) {
                $row['is_in_stock'] = false;
                $row['stock_status_changed_auto'] = false;
            } else {
                $row['stock_status_changed_auto'] =
                    (int)!$this->stockStateProvider->verifyStock($stockItemDo);
            }
        } else {
            $row['qty'] = 0;
        }

        return $row;
    }

    /**
     * get product qty
     *
     * @param string $sku
     *
     * @return float
     */
    protected function getProductStockQty($sku)
    {
        try {
            $stockItem = $this->stockRegistry->getStockItemBySku($sku);
            $qty = $stockItem->getData('qty');
        } catch (NoSuchEntityException $e) {
            $qty = 0;
        }

        return $qty;
    }

    /**
     * Set valid attribute set and product type to rows with all scopes
     * to ensure that existing products doesn't changed.
     *
     * @param array $rowData
     *
     * @return array
     */
    protected function _prepareRowForDb(array $rowData)
    {
        $productType = isset($rowData[self::COL_TYPE]) ? $rowData[self::COL_TYPE] : '';
        $rowData = parent::_prepareRowForDb($rowData);
        if ($productType) {
            $rowData[self::COL_TYPE] = $productType;
        }
        if (!$this->onlyUpdate) {
            foreach ($this->defaultStockData as $key => $value) {
                if (isset($rowData[$key])) {
                    if ($rowData[$key] === true) {
                        $rowData[$key] = 1;
                    } elseif ($rowData[$key] === false) {
                        $rowData[$key] = 0;
                    } elseif ($rowData[$key] === '') {
                        $rowData[$key] = 0;
                    } elseif ($rowData[$key] === null && $key != 'low_stock_date') {
                        $rowData[$key] = 0;
                    }
                }
            }
        }
        $rowData = $this->adjustBundleTypeAttributes($rowData);
        return $rowData;
    }

    /**
     * @param array $rowData
     * @return mixed
     */
    public function preparePlatformRowForDb(array $rowData)
    {
        return $this->_prepareRowForDb($rowData);
    }

    /**
     * @param array $rowData
     * @param array $attrs
     * @param array $configurableData
     */
    protected function prepareConfigurableVariation(
        array $rowData,
        array $attrs,
        $visibilityData,
        array &$configurableData = []
    ) {
        if (!empty($this->_parameters['configurable_switch']) && isset($rowData['product_type'])
            && ($rowData['product_type'] == 'simple' || $rowData['product_type'] == 'virtual')) {
            $simpleValAttr = empty($this->_parameters['copy_simple_value']) ? []
                : array_column($this->_parameters['copy_simple_value'], 'copy_simple_value_attributes');
            $field = $this->_parameters['configurable_field'];
            $skuConf = null;
            if (isset($rowData[$field])) {
                switch ($this->_parameters['configurable_type']) {
                    case TypeOptions::FIELD:
                        if ($rowData[$field] && $this->getCorrectSkuAsPerLength($rowData) != $rowData[$field]) {
                            $skuConf = $rowData[$field];
                        }
                        break;
                    case TypeOptions::PART_UP:
                        $array = explode($this->_parameters['configurable_part'], $rowData[$field]);
                        if (count($array) > 1) {
                            $skuConf = $array[0];
                        }
                        break;
                    case TypeOptions::PART_DOWN:
                        $array = explode($this->_parameters['configurable_part'], $rowData[$field]);
                        if (count($array) > 1) {
                            $skuConf = $array[count($array) - 1];
                        }
                        break;
                    case TypeOptions::SUB_UP:
                        if (!empty($this->_parameters['configurable_symbols'])) {
                            $skuConf = substr($rowData[$field], 0, (int)$this->_parameters['configurable_symbols']);
                        }
                        break;
                    case TypeOptions::SUB_DOWN:
                        if (!empty($this->_parameters['configurable_symbols'])) {
                            $skuConf = substr($rowData[$field], -(int)$this->_parameters['configurable_symbols']);
                        }
                        break;
                }
            }
            if ($this->_replaceFlag && !isset($this->_oldSku[mb_strtolower($skuConf)])) {
                $skuConf = null;
            }
            if ($skuConf) {
                $newData = $rowData;
                $arrayConf = [];
                if (!empty($this->_parameters['configurable_variations'])) {
                    foreach ($this->_parameters['configurable_variations'] as $attrField) {
                        if (isset($newData[$attrField]) && trim($newData[$attrField]) !== '') {
                            $arrayConf[$attrField] = $newData[$attrField];
                        }
                    }
                }
                if (!empty($arrayConf)) {
                    $arrayConf['sku'] = (string) $newData['sku'];
                    if (in_array(ProductInterface::VISIBILITY, $simpleValAttr)) {
                        $arrayConf[Product\ConfigurationVariations::FIELD_COPY_VALUE][ProductInterface::VISIBILITY] =
                            $attrs[ProductInterface::VISIBILITY] ?? Visibility::VISIBILITY_BOTH;
                    }
                    if (in_array(ProductInterface::STATUS, $simpleValAttr)) {
                        $arrayConf[Product\ConfigurationVariations::FIELD_COPY_VALUE][ProductInterface::STATUS] =
                            $attrs[ProductInterface::STATUS] ?? Status::STATUS_ENABLED;
                    }

                    if ($visibilityData['doesVisibilitySet'] && isset($attrs[ProductInterface::VISIBILITY])) {
                        $arrayConf[ProductInterface::VISIBILITY] = $attrs[ProductInterface::VISIBILITY];
                    }

                    if ($visibilityData['doesStatusSet'] && isset($attrs[ProductInterface::STATUS])) {
                        $arrayConf[ProductInterface::STATUS] = $attrs[ProductInterface::STATUS];
                    }

                    $arrayConf[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['website_ids']
                        = $this->configVariations->getWebsiteArray($rowData, $this->getMultipleValueSeparator());
                    $arrayConf[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['attribute_set_id']
                        = $this->configVariations->getAttributeSetIdBySku($rowData, $this->_attrSetNameToId);
                    foreach ($simpleValAttr as $attrCode) {
                        if ($attrCode == 'category_ids' && isset($rowData[$attrCode])) {
                            $categoryIds = explode($this->getMultipleValueSeparator(), $rowData[$attrCode]);
                            if (!empty($categoryIds) && !is_array($categoryIds)) {
                                $categoryIds[] = $categoryIds;
                            }
                            $categoriesForCopy = [];
                            foreach ($categoryIds as $categoryId) {
                                $categoryId = (int)$categoryId;
                                $existingCategory = $this->categoryProcessor->getCategoryById($categoryId);
                                if (!empty($existingCategory) && $existingCategory) {
                                    $categoriesForCopy[] = $categoryId;
                                }
                            }
                            $arrayConf[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['category_ids'] =
                                $categoriesForCopy;
                            continue;
                        } elseif ($attrCode == 'additional_images') {
                            $bunchUploadedImages = $this->importImageProcessor->getBunchUploadedImages();
                            foreach (explode($this->getMultipleValueSeparator(), $rowData['_media_image']) as $image) {
                                $arrayConf[Product\ConfigurationVariations::FIELD_COPY_VALUE]['_media_image'][] =
                                    $bunchUploadedImages[$image] ?? '';
                            }
                            continue;
                        } elseif ($attrCode == 'additional_image_labels') {
                            $arrayConf[Product\ConfigurationVariations::FIELD_COPY_VALUE]['_media_image_label'] =
                                $rowData['_media_image_label'] ?? '';
                            continue;
                        } elseif ($attrCode == 'category_ids' && isset($this->categoriesCache[$rowData['sku']])) {
                            $arrayConf[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['category_ids'] =
                                array_keys($this->categoriesCache[$rowData['sku']]);
                            continue;
                        } elseif ($attrCode == SystemOptions::RELATED_PRODUCT_ATTRIBUTE ||
                            $attrCode == SystemOptions::CROSS_SELLS_PRODUCT_ATTRIBUTE ||
                            $attrCode == SystemOptions::UP_SELLS_PRODUCT_ATTRIBUTE) {
                            $arrayConf[Product\ConfigurationVariations::FIELD_COPY_VALUE][$attrCode] =
                                $rowData[$attrCode] ?? '';
                            continue;
                        }
                        if (isset($attrs[$attrCode])) {
                            $arrayConf[Product\ConfigurationVariations::FIELD_COPY_VALUE][$attrCode] =
                                $attrs[$attrCode] ?? '';
                        }
                    }
                    $simpleProductImage = [];
                    if (!empty($this->_parameters['configurable_create'])) {
                        $simpleProductImage = $this->configVariations->getAttrsImage($attrs, $this->_imagesArrayKeys);
                    }
                    $arrayConf[Product\ConfigurationVariations::FIELD_COPY_VALUE] = array_merge(
                        ($arrayConf[Product\ConfigurationVariations::FIELD_COPY_VALUE] ?? []),
                        $simpleProductImage
                    );
                    $configurableData[(string) $skuConf][] = $arrayConf;
                }
            }
        }
    }

    /**
     * @param string $key
     * @param array $bunch
     * @return void
     */
    protected function removeFromBunch(string $key, &$bunch = [])
    {
        foreach ($bunch as $k => $rowData) {
            unset($bunch[$k][$key]);
        }
    }

    /**
     * @param string $key
     * @return bool
     */
    protected function findInMap(string $key)
    {
        foreach ($this->_parameters['map'] as $map) {
            if ($map['system'] == $key || $map['import'] == $key) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param array $rowData
     * @param bool $validateBunch
     * @return array
     */
    protected function useOnlyFieldsFromMapping(&$rowData = [], $validateBunch = false)
    {
        if (empty($this->_parameters['map'])) {
            return $rowData;
        }
        if ($validateBunch) {
            $checklist = ['custom_options', 'upsell_skus', 'crosssell_skus', 'related_skus', '_upsell_sku',
                '_crosssell_sku', '_related_sku', 'configurable_variations'];
            foreach ($checklist as $key => $param) {
                $this->findInMap($param) ?:
                    $this->removeFromBunch($param, $rowData);
            }
            return $rowData;
        }
        $requiredFields = ['product_type' => 'simple', '_attribute_set' => 'Default',
            '_product_websites' => 'base'];
        if (!empty($rowData['product_online'])) {
            $requiredFields['status'] = $rowData['product_online'];
        }
        $rowDataAfterMapping = [];
        foreach ($this->_parameters['map'] as $parameter) {
            if (array_key_exists($parameter['import'], $rowData)) {
                $rowDataAfterMapping[$parameter['system']] = $rowData[$parameter['import']];
            }
        }
        if (!empty($rowDataAfterMapping['additional_attributes'])) {
            $rowDataAfterMapping = $this->_parseAdditionalAttributes($rowDataAfterMapping);
        }
        empty($rowDataAfterMapping['base_image_label']) ?:
            $rowDataAfterMapping['image_label'] = $rowDataAfterMapping['base_image_label'];
        foreach ($requiredFields as $k => $value) {
            $rowDataAfterMapping[$k] = !empty($rowData[$k]) ? $rowData[$k] : $value;
        }
        return $rowDataAfterMapping;
    }

    /**
     * Gather and save information about product entities.
     *
     * @return $this
     * @throws ConnectionException
     * @throws DeadlockException
     * @throws LocalizedException
     * @throws LockWaitException
     * @throws NoSuchEntityException
     * @throws \Magento\Framework\Exception\CouldNotSaveException
     * @throws \Magento\Framework\Exception\InputException
     * @throws \Magento\Framework\Exception\StateException
     */
    protected function saveProducts()
    {
        $this->translator = $this->translator->init($this->_parameters);
        $existingImages = [];
        $existingUpload = [];
        $entityLinkField = $this->getProductEntityLinkField();
        if (!empty($this->_parameters['import_source']) && $this->_parameters['import_source'] != 'file') {
            $this->_initSourceType($this->_parameters['import_source']);
        }
        $configurableData = [];

        $isPriceGlobal = $this->_catalogData->isPriceGlobal();
        $productLimit = null;
        $productsQty = null;
        $this->importImage->setConfig($this->_parameters);

        while ($nextBunch = $this->_dataSourceModel->getNextBunch()) {
            $entityRowsIn = $entityRowsUp = [];
            $attributes = [];
            $attributeDefaultData = [];
            $this->websitesCache = $this->categoriesCache = $this->categoryProductPosition = [];
            $this->categoryProcessor->setRowCategoryPosition($this->categoryProductPosition);
            $mediaGallery = $uploadedImages = [];
            $tierPrices = [];
            $previousType = $prevAttributeSet = null;
            $existingImages = $this->getExistingImages($nextBunch);
            $existingAttributeImages = $this->importImageProcessor->getExistingAttributeImages($nextBunch);
            if ($this->sourceType && $this->_parameters['image_import_source']) {
                $nextBunch = $this->prepareImagesFromSource($nextBunch);
            }

            $prevData = [];
            $createValuesAllowed = (bool)$this->scopeConfig->getValue(
                Import::CREATE_ATTRIBUTES_CONF_PATH,
                ScopeInterface::SCOPE_STORE
            );
            $storeIds = $this->getStoreIds();
            foreach ($nextBunch as $rowNum => $rowData) {
                $time = explode(" ", microtime());
                $startTime = $time[0] + $time[1];
                $mappedCategories = [];
                $mappedCategoriesPosition = [];
                if (isset($rowData[self::COL_CATEGORY])) {
                    $categoriesMapping = $this->categoriesMapping($rowData);
                    $rowData[self::COL_CATEGORY] = $categoriesMapping[self::COL_CATEGORY];
                    $rowData[self::COL_CATEGORY . '_position'] =
                        $categoriesMapping[self::COL_CATEGORY . '_position'];
                    $mappedCategories = $categoriesMapping['mappedCategories'];
                    $mappedCategoriesPosition = $categoriesMapping['mappedCategoriesPosition'];
                }

                $rowData = $this->joinIdenticalyData($rowData);
                if (isset($rowData['_attribute_set']) && isset($rowData['attribute_set_code'])) {
                    if (isset($rowData['update_attribute_set']) && ((int)($rowData['update_attribute_set']) > 0)) {
                        $rowData['_attribute_set'] = $rowData['attribute_set_code'];
                    } else {
                        unset($rowData['attribute_set_code']);
                    }
                }
                $oldSkus = $this->skuProcessor->getOldSkus();
                $sku = strtolower($this->getCorrectSkuAsPerLength($rowData));

                if (!isset($oldSkus[$sku])) {
                    if (!isset($rowData['_attribute_set'])
                        || (isset($rowData['_attribute_set']) && empty($rowData['_attribute_set']))
                    ) {
                        $collectSets = $this->_attrSetIdToName;
                        reset($collectSets);
                        $rowData['_attribute_set'] = current($collectSets);
                    }
                }

                if (isset($this->_parameters['remove_related_product'])
                    && $this->_parameters['remove_related_product'] == 1
                ) {
                    $this->removeRelatedProducts($this->getCorrectSkuAsPerLength($rowData));
                }

                if (isset($this->_parameters['remove_crosssell_product'])
                    && $this->_parameters['remove_crosssell_product'] == 1
                ) {
                    $this->removeCrosssellProducts($this->getCorrectSkuAsPerLength($rowData));
                }

                if (isset($this->_parameters['remove_upsell_product'])
                    && $this->_parameters['remove_upsell_product'] == 1
                ) {
                    $this->removeUpsellProducts($this->getCorrectSkuAsPerLength($rowData));
                }

                $rowData = $this->checkAdditionalImages($rowData);
                $rowData = $this->customChangeData($rowData);
                $rowData = $this->applyCategoryLevelSeparator($rowData);

                if (!$this->validateRow($rowData, $rowNum) || !$this->validateRowByProductType($rowData, $rowNum)) {
                    $this->addLogWriteln(
                        __('product with sku: %1 is not valided', $this->getCorrectSkuAsPerLength($rowData)),
                        $this->output,
                        'info'
                    );
                    $this->notValidedSku[] = strtolower($this->getCorrectSkuAsPerLength($rowData));
                    unset($nextBunch[$rowNum]);
                    continue;
                } else {
                    $rowData = $this->stripSlashes($rowData);
                }

                $productType = isset($rowData[self::COL_TYPE]) ?
                    strtolower($rowData[self::COL_TYPE]) :
                    $this->skuProcessor->getNewSku($this->getCorrectSkuAsPerLength($rowData))['type_id'];
                // custom
                if ($productType) {
                    $productTypeModel = $this->_productTypeModels[$productType];
                    if ($createValuesAllowed) {
                        $rowData = $this->createAttributeValues(
                            $productTypeModel,
                            $rowData
                        );
                    }
                }

                if (!isset($rowData[self::COL_ATTR_SET]) ||
                    !isset($this->_attrSetNameToId[$rowData[self::COL_ATTR_SET]])) {
                    $this->addRowError(ValidatorInterface::ERROR_INVALID_ATTR_SET, $rowNum);
                    $this->addLogWriteln(
                        __(
                            'product with sku: %1 is not valided. ' .
                            'Invalid value for Attribute Set column (set doesn\'t exist?)',
                            $this->getCorrectSkuAsPerLength($rowData)
                        ),
                        $this->output,
                        'info'
                    );
                    $this->notValidedSku[] = strtolower($this->getCorrectSkuAsPerLength($rowData));
                    unset($nextBunch[$rowNum]);
                    continue;
                }
                $urlKey = null;
                $isUpdate = $this->onlyUpdate || isset($this->_oldSku[$sku]);
                if (!($isUpdate && empty($rowData[self::URL_KEY]))) {
                    $urlKey = $this->getProductUrlKey($rowData);
                }

                if ($urlKey) {
                    if (!empty($rowData[self::URL_KEY])) {
                        // If url_key column and its value were in the CSV file
                        $rowData[self::URL_KEY] = $urlKey;
                    } elseif ($this->isNeedToChangeUrlKey($rowData)) {
                        // If url_key column was empty or even not declared in the CSV file but by the rules
                        // it is need to be setteed. In case when url_key is generating from name column we
                        // have to ensure that the bunch of products will pass for the event with url_key column.
                        $nextBunch[$rowNum][self::URL_KEY] = $rowData[self::URL_KEY] = $urlKey;
                    } elseif (isset($rowData[self::URL_KEY]) || isset($rowData[self::COL_NAME])) {
                        $rowData[self::URL_KEY] = $urlKey;
                    }
                }

                $this->urlKeys = [];
                $rowData = $this->adjustBundleTypeAttributes($rowData);

                if (empty($this->getCorrectSkuAsPerLength($rowData))) {
                    $rowData = array_merge($prevData, $this->deleteEmpty($rowData));
                } else {
                    $prevData = $rowData;
                }
                $sku = $this->getCorrectSkuAsPerLength($rowData);
                if ($this->onlyUpdate) {
                    $collectionUpdate = $this->collectionFactory->create()->addFieldToFilter(
                        self::COL_SKU,
                        $this->getCorrectSkuAsPerLength($rowData)
                    );
                    if (!$collectionUpdate->getSize()) {
                        $this->addLogWriteln(__('product with sku: %1 does not exist', $sku), $this->output, 'info');
                        unset($nextBunch[$rowNum]);
                        continue;
                    }
                }
                if ($this->getErrorAggregator()->isErrorLimitExceeded()) {
                    $this->getErrorAggregator()->addRowToSkip($rowNum);
                    unset($nextBunch[$rowNum]);
                    $this->notValidedSku[] = strtolower($this->getCorrectSkuAsPerLength($rowData));

                    continue;
                }

                if (isset($rowData['_attribute_set']) && isset($this->_attrSetNameToId[$rowData['_attribute_set']])) {
                    $this->skuProcessor->setNewSkuData(
                        $this->getCorrectSkuAsPerLength($rowData),
                        'attr_set_id',
                        $this->_attrSetNameToId[$rowData['_attribute_set']]
                    );
                }
                $rowScope = $this->getRowScope($rowData);
                $rowSku = $this->getCorrectSkuAsPerLength($rowData);
                $checkSku = $rowSku;

                if (version_compare($this->productMetadata->getVersion(), '2.2.0', '>=')) {
                    $checkSku = strtolower($rowSku);
                }
                if (!$rowSku) {
                    $this->getErrorAggregator()->addRowToSkip($rowNum);
                    continue;
                } elseif (self::SCOPE_STORE == $rowScope) {
                    // set necessary data from SCOPE_DEFAULT row
                    $rowData[self::COL_TYPE] = $this->skuProcessor->getNewSku($checkSku)['type_id'];
                    $rowData['attribute_set_id'] = $this->skuProcessor->getNewSku($checkSku)['attr_set_id'];
                    $rowData[self::COL_ATTR_SET] = $this->skuProcessor->getNewSku($checkSku)['attr_set_code'];
                }

                // Entity phase
                if (!isset($this->_oldSku[$checkSku])) {
                    // new row
                    if (!$productLimit || $productsQty < $productLimit) {
                        if (isset($rowData['has_options'])) {
                            $hasOptions = $rowData['has_options'];
                        } else {
                            $hasOptions = 0;
                        }
                        $entityRowsIn[$rowSku] = [
                            'attribute_set_id' => $this->skuProcessor->getNewSku($checkSku)['attr_set_id'],
                            'type_id' => $this->skuProcessor->getNewSku($checkSku)['type_id'],
                            'sku' => $rowSku,
                            'has_options' => $hasOptions,
                            'created_at' => $this->_localeDate->date()->format(DateTime::DATETIME_PHP_FORMAT),
                            'updated_at' => $this->_localeDate->date()->format(DateTime::DATETIME_PHP_FORMAT),
                        ];
                        $productsQty++;
                    } else {
                        $rowSku = null;
                        // sign for child rows to be skipped
                        $this->getErrorAggregator()->addRowToSkip($rowNum);
                        continue;
                    }
                } else {
                    $array = [
                        'updated_at' => $this->_localeDate->date()->format(DateTime::DATETIME_PHP_FORMAT),
                        $entityLinkField => $this->_oldSku[$checkSku][$entityLinkField],
                    ];
                    $array['attribute_set_id'] = $this->skuProcessor->getNewSku($checkSku)['attr_set_id'];
                    $array['type_id'] = $productType;
                    // existing row
                    $entityRowsUp[] = $array;
                }

                // Categories phase
                if (!array_key_exists($rowSku, $this->categoriesCache)) {
                    $this->categoriesCache[$rowSku] = [];
                }

                $rowData['rowNum'] = $rowNum;
                $categoryIds = $this->getCategories($rowData);
                if (isset($rowData['category_ids'])) {
                    $catIds = explode($this->getMultipleValueSeparator(), $rowData['category_ids']);
                    $finalCatId = [];
                    foreach ($catIds as $catId) {
                        $catId = (int)$catId;
                        $existingCat = $this->categoryProcessor->getCategoryById($catId);
                        if (is_int($catId) && $catId > 0 && $existingCat && $existingCat->getId()) {
                            $finalCatId[] = $catId;
                        }
                    }
                    $categoryIds = array_merge($categoryIds, $finalCatId);
                }

                $categoryIds = array_merge($categoryIds, $mappedCategories);

                foreach ($categoryIds as $id) {
                    $this->categoriesCache[$rowSku][$id] = true;
                }

                $catIds = [];
                if ($this->isSkuExist($rowSku)) {
                    if (!isset($this->_oldSku[strtolower($rowData[self::COL_SKU])]['entity_id'])) {
                        $entityId = $this->_oldSku[strtolower($rowData[self::COL_SKU])]['row_id'];
                        $this->skuProcessor->setNewSkuData($rowData[self::COL_SKU], 'entity_id', $entityId);
                    } else {
                        $entityId = $this->_oldSku[strtolower($rowData[self::COL_SKU])]['entity_id'];
                    }
                    $oldCategoryIds = $this->getCategoryLinks($entityId);

                    if (!empty($categoryIds)
                        && isset(
                            $this->_parameters['remove_product_categories'],
                            $this->_oldSku[strtolower($rowData[self::COL_SKU])]
                        )
                        && $this->_parameters['remove_product_categories'] > 0
                    ) {
                        foreach ($oldCategoryIds as $oldCategoryId) {
                            if (!in_array($oldCategoryId['category_id'], $categoryIds, false)) {
                                $this->categoriesCache[$rowSku][$oldCategoryId['category_id']] = false;
                            }
                        }
                    }
                }

                if (!isset($this->categoryProductPosition[$rowSku])) {
                    $this->categoryProductPosition[$rowSku] = [];
                }
                $this->categoryProductPosition[$rowSku] += $this->categoryProcessor->getRowCategoryPosition();
                $this->categoryProductPosition[$rowSku] += $mappedCategoriesPosition;

                if (isset($rowData[self::COL_CATEGORY]) && empty($rowData[self::COL_CATEGORY])) {
                    foreach ($catIds as $categoryId) {
                        $this->categoryLinkRepository->deleteByIds($categoryId, $rowData[self::COL_SKU]);
                        $this->categoriesCache[$rowSku] = [];
                        $this->categoryProductPosition[$rowSku] = [];
                    }
                }

                unset($rowData['rowNum']);
                if (!array_key_exists($rowSku, $this->websitesCache)) {
                    $this->websitesCache[$rowSku] = [];
                }
                // Product-to-Website phase
                if (!empty($rowData[self::COL_PRODUCT_WEBSITES])) {
                    $websiteCodes = explode($this->getMultipleValueSeparator(), $rowData[self::COL_PRODUCT_WEBSITES]);
                    foreach ($websiteCodes as $websiteCode) {
                        $websiteId = $this->storeResolver->getWebsiteCodeToId($websiteCode);
                        $this->websitesCache[$rowSku][$websiteId] = true;
                    }
                }
                // Price rules
                $rowData = $this->applyPriceRules($rowData);
                $fixedName = __("Fixed");
                $fixed = $fixedName;
                if (isset($rowData['_tier_price_value_type'])) {
                    $fixed = $rowData['_tier_price_value_type'] == $fixedName;
                }
                // Tier prices phase
                if (!empty($rowData['_tier_price_website'])) {
                    $tierPrices[$rowSku][] = [
                        'all_groups' => $rowData['_tier_price_customer_group'] == self::VALUE_ALL,
                        'customer_group_id' => $rowData['_tier_price_customer_group'] ==
                        self::VALUE_ALL ? 0 : $rowData['_tier_price_customer_group'],
                        'qty' => $rowData['_tier_price_qty'],
                        'value' => ($fixed) ? $rowData['_tier_price_price'] : 0,
                        'website_id' => self::VALUE_ALL == $rowData['_tier_price_website'] || $isPriceGlobal
                            ? 0
                            : $this->storeResolver->getWebsiteCodeToId(
                                $rowData['_tier_price_website']
                            ),
                        'percentage_value' => (!$fixed) ? $rowData['_tier_price_price'] : 0,
                    ];
                    $tierPrices = array_merge($tierPrices, $this->getTierPrices($rowData, $rowSku));
                } else {
                    $tierPrices += $this->getTierPrices($rowData, $rowSku);
                }
                if (!$this->validateRow($rowData, $rowNum)) {
                    $this->addLogWriteln(__('product with sku: %1 is not valided', $sku), $this->output, 'info');
                    unset($nextBunch[$rowNum]);
                    continue;
                }
                // Media gallery phase
                if ($this->publisher && isset($this->_parameters['deferred_images']) &&
                    $this->_parameters['deferred_images']) {
                    $this->importImage->addMediaGalleryRows($rowData);
                } else {
                    $this->importImageProcessor->setConfig($this->_parameters);
                    $this->processMediaGalleryRows(
                        $rowData,
                        $mediaGallery,
                        $existingImages,
                        $uploadedImages,
                        $rowNum,
                        $existingAttributeImages
                    );
                }

                if (!$productType === null) {
                    $previousType = $productType;
                }
                $prevAttributeSet = null;
                if (isset($rowData[self::COL_ATTR_SET])) {
                    $prevAttributeSet = $rowData[self::COL_ATTR_SET];
                }
                if (self::SCOPE_NULL == $rowScope) {
                    // for multiselect attributes only
                    if (!$prevAttributeSet === null) {
                        $rowData[self::COL_ATTR_SET] = $prevAttributeSet;
                    }
                    if ($productType === null && !$previousType === null) {
                        $productType = $previousType;
                    }
                    if ($productType === null) {
                        continue;
                    }
                }
                if (!$productType) {
                    $tempProduct = $this->skuProcessor->getNewSku($checkSku);
                    if (isset($tempProduct['type_id'])) {
                        $productType = $tempProduct['type_id'];
                    }
                }
                if ($productType) {
                    $rowScope = empty($rowData[self::COL_STORE]) ? self::SCOPE_DEFAULT : self::SCOPE_STORE;
                    $rowStore = (self::SCOPE_STORE == $rowScope)
                        ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE])
                        : 0;
                    $productTypeModel = $this->_productTypeModels[$productType];

                    if (!empty($rowData['tax_class_name'])) {
                        $rowData['tax_class_name'] = $this->getCurrentTaxClass($rowData['tax_class_name']);
                        $rowData['tax_class_id'] =
                            $this->taxClassProcessor->upsertTaxClass($rowData['tax_class_name'], $productTypeModel);
                    }

                    if ($this->getBehavior() == Import::BEHAVIOR_APPEND ||
                        empty($this->getCorrectSkuAsPerLength($rowData))) {
                        if (isset($this->_parameters['clear_attribute_value'])
                            && $this->_parameters['clear_attribute_value'] == 0) {
                            $rowData = $productTypeModel->clearEmptyData($rowData);
                        }
                    }

                    if (isset($this->_parameters['clear_attribute_value'])
                        && $this->_parameters['clear_attribute_value'] == 1
                    ) {
                        $rowData[self::COL_STORE] = null;
                    }

                    $visibilityData = [
                        'doesStatusSet' => false,
                        'doesVisibilitySet' => false
                    ];
                    if (isset($rowData[ProductInterface::STATUS])) {
                        $visibilityData['doesStatusSet'] = true;
                    }
                    if (isset($rowData[ProductInterface::VISIBILITY])) {
                        $visibilityData['doesVisibilitySet'] = true;
                    }

                    $rowData = $productTypeModel->prepareAttributesWithDefaultValueForSave(
                        $rowData,
                        !isset($this->_oldSku[$checkSku])
                    );
                    $this->prepareConfigurableVariation($prevData, $rowData, $visibilityData, $configurableData);

                    //google translation data
                    if ($this->translator->isTranslatorSet()) {
                        $translateAttributes = $this->_parameters['translate_attributes'] ?? [];
                        $translateStore = (int)($this->_parameters['translate_store_ids'] ?? 0);
                    }

                    $skuLower = strtolower($rowSku);

                    // retrieves attributes
                    $attributeList = [];
                    foreach ($rowData as $attrCode => $attrValue) {
                        $attributeList[$attrCode] = $this->retrieveAttributeByCode($attrCode);
                    }

                    // attributes default values
                    $attributesDefaultValues = $this->getDefaultAttributesValue($attributeList, $skuLower, $rowStore);

                    foreach ($rowData as $attrCode => $attrValue) {
                        $attribute = $this->retrieveAttributeByCode($attrCode);
                        if ('multiselect' != $attribute->getFrontendInput() && self::SCOPE_NULL == $rowScope) {
                            // skip attribute processing for SCOPE_NULL rows
                            continue;
                        }
                        $attrId = $attribute->getId();
                        $backModel = $attribute->getBackendModel();
                        $attrTable = $attribute->getBackend()->getTable();
                        $storeIds = [0];

                        if ('datetime' == $attribute->getBackendType()
                            && (
                                in_array($attribute->getAttributeCode(), $this->dateAttrCodes)
                                || $attribute->getIsUserDefined()
                            )
                        ) {
                            $attrValue = $this->dateTime->formatDate($attrValue, false);
                        } elseif ('datetime' == $attribute->getBackendType() && strtotime($attrValue)) {
                            $attrValue = gmdate(
                                'Y-m-d H:i:s',
                                $this->_localeDate->date($attrValue)->getTimestamp()
                            );
                        }

                        $defaultValue = $attributesDefaultValues[
                            $this->getAttributeDefaultValueKey($skuLower, $attribute->getId(), 0)
                            ] ?? false;
                        $storeValue = $attributesDefaultValues[
                            $this->getAttributeDefaultValueKey($skuLower, $attribute->getId(), $rowStore)
                            ] ?? false;

                        if (!isset($this->adminAttributeValue[$rowSku])) {
                            $this->adminAttributeValue = [$rowSku => []];
                        }

                        if (false === $defaultValue && $rowStore == 0) {
                            $this->adminAttributeValue[$rowSku][$attrCode] = $attrValue;
                        }

                        /*
                         * If storeValue exists and the default value is same as new value then remove it
                         */
                        if ($storeValue && $defaultValue === (string)$attrValue && $rowStore > 0) {
                            if (isset($attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID])) {
                                $defaultStoreIdValue =
                                    $attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID];
                                if ($defaultStoreIdValue === (string)$attrValue) {
                                    $this->_deleteStoreAttributeValue($attribute, $rowSku, $rowStore);
                                }
                            } else {
                                $this->_deleteStoreAttributeValue($attribute, $rowSku, $rowStore);
                            }
                        }

                        if ($storeValue && (float)$defaultValue === (float)$attrValue
                            && $rowStore > 0 && is_numeric($attrValue)) {
                            if (isset($attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID])) {
                                $defaultStoreIdValue =
                                    $attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID];
                                if ((float)$defaultStoreIdValue === (float)$attrValue) {
                                    $this->_deleteStoreAttributeValue($attribute, $rowSku, $rowStore);
                                }
                            } else {
                                $this->_deleteStoreAttributeValue($attribute, $rowSku, $rowStore);
                            }
                        }

                        if ($this->translator->isTranslatorSet()) {
                            if (!empty($translateAttributes)
                                && !empty($translateStore)
                                && !isset($attributes[$attrTable][$rowSku][$attrId][$translateStore])
                                && in_array($attrCode, $translateAttributes, true)
                            ) {
                                $storeValue = $this->translator
                                    ->translateAttributeValue($attrValue, $attrCode, $translateStore);
                                $attributes[$attrTable][$rowSku][$attrId][$translateStore] = $storeValue;
                            } elseif (isset($translateStore) && $translateStore > 0) {
                                $this->_deleteStoreAttributeValue($attribute, $rowSku, $translateStore);
                            }
                        }
                        if ($defaultValue && ($defaultValue === (string)$attrValue)) {
                            if (isset($attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID])) {
                                $defaultStoreIdValue =
                                    $attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID];
                                if ($defaultStoreIdValue === (string)$attrValue) {
                                    $attributeDefaultData[$attrTable][$rowSku][$attrId] = true;
                                    continue;
                                }
                            } else {
                                $attributeDefaultData[$attrTable][$rowSku][$attrId] = true;
                                continue;
                            }
                        }

                        if ($defaultValue && ((float)$defaultValue === (float)$attrValue)
                            && is_numeric($attrValue)) {
                            if (isset($attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID])) {
                                $defaultStoreIdValue =
                                    $attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID];
                                if ((float)$defaultStoreIdValue === (float)$attrValue) {
                                    $attributeDefaultData[$attrTable][$rowSku][$attrId] = true;
                                    continue;
                                }
                            } else {
                                $attributeDefaultData[$attrTable][$rowSku][$attrId] = true;
                                continue;
                            }
                        }

                        $adminValue = $this->adminAttributeValue[$rowSku][$attrCode] ?? false;
                        if (false !== $adminValue && $adminValue === $attrValue && $rowStore > 0) {
                            continue;
                        }

                        if (self::SCOPE_STORE == $rowScope) {
                            if (self::SCOPE_WEBSITE == $attribute->getIsGlobal()) {
                                // check website defaults already set
                                if (!isset($attributes[$attrTable][$rowSku][$attrId][$rowStore])) {
                                    $storeIds = $this->prepareStoreIdToWebsiteStoreIds(
                                        $this->storeResolver->getStoreIdToWebsiteStoreIds($rowStore)
                                    );
                                }
                            } elseif (self::SCOPE_STORE == $attribute->getIsGlobal()) {
                                $storeIds = [$rowStore];
                            } elseif (self::SCOPE_DEFAULT == $attribute->getIsGlobal()) {
                                $storeIds[] = Store::DEFAULT_STORE_ID;
                            }

                            if (!isset($this->_oldSku[$checkSku])) {
                                $storeIds[] = Store::DEFAULT_STORE_ID;
                            }
                        }
                        $storeIds = array_unique($storeIds);
                        sort($storeIds);

                        foreach ($storeIds as $storeId) {
                            if ($storeId == Store::DEFAULT_STORE_ID) {
                                if (isset($attributeDefaultData[$attrTable][$rowSku][$attrId])) {
                                    continue;
                                }
                            }
                            if (!isset($attributes[$attrTable][$rowSku][$attrId][$storeId])) {
                                if (isset($this->_oldSku[$checkSku])
                                    && in_array($attrCode, ['image', 'small_image', 'thumbnail', 'swatch_image'])
                                    && !in_array(Store::DEFAULT_STORE_ID, $storeIds) && !$defaultValue) {
                                    $attributes[$attrTable][$rowSku][$attrId][Store::DEFAULT_STORE_ID] = $attrValue;
                                } else {
                                    $attributes[$attrTable][$rowSku][$attrId][$storeId] = $attrValue;
                                }
                            }
                        }
                        // restore 'backend_model' to avoid 'default' setting
                        $attribute->setBackendModel($backModel);
                    }

                    $time = explode(" ", microtime());
                    $endTime = $time[0] + $time[1];
                    $totalTime = $endTime - $startTime;
                    $totalTime = round($totalTime, 5);
                    $this->addLogWriteln(__('product with sku: %1 .... %2s', $sku, $totalTime), $this->output, 'info');
                }
            }
            if (method_exists($this, '_saveProductEntity')) {
                $this->_saveProductEntity(
                    $entityRowsIn,
                    $entityRowsUp
                );
            } else {
                $this->saveProductEntity(
                    $entityRowsIn,
                    $entityRowsUp
                );
            }

            $isCached = $this->_parameters['cache_products'] ?? false;
            if ($isCached) {
                $this->saveProductsCache($entityRowsIn, $entityRowsUp);
            }
            $this->afterSaveNewEntities($entityRowsIn);
            $this->addLogWriteln(__('Imported: %1 rows', count($entityRowsIn)), $this->output, 'info');
            $this->addLogWriteln(__('Updated: %1 rows', count($entityRowsUp)), $this->output, 'info');
            $this->_saveProductWebsites(
                $this->websitesCache
            )->_saveProductCategories(
                $this->categoriesCache
            )->_saveProductTierPrices(
                $tierPrices
            );
            if ($this->publisher && isset($this->_parameters['deferred_images']) &&
                $this->_parameters['deferred_images']) {
                $this->importImage->publishBranch();
            } else {
                $this->_saveMediaGallery($mediaGallery);
                if (!empty($this->_parameters['image_resize'])) {
                    $this->addLogWriteln(__('Start resizing images for the bunch'), $this->getOutput(), 'info');
                    $this->importImageProcessor->processImageResize();
                    $this->addLogWriteln(__('Resizing images for the bunch is complete'), $this->getOutput(), 'info');
                }
            }
            $this->_saveProductAttributes($attributes);
            $this->_saveProductCategoriesPosition($this->categoryProductPosition);

            $this->_eventManager->dispatch(
                'catalog_product_import_bunch_save_after',
                ['adapter' => $this, 'bunch' => $nextBunch]
            );
        }
        if (!empty($configurableData)) {
            $this->saveConfigurationVariations($configurableData, $existingImages);
        }

        $this->cache->clean([ImportProductCache::BUFF_CACHE]);
        return $this;
    }

    /**
     * @param $storeIds
     * @return array
     */
    protected function prepareStoreIdToWebsiteStoreIds($storeIds)
    {
        if (is_array($storeIds)) {
            foreach ($storeIds as $key => $storeId) {
                if (is_array($storeId)) {
                    foreach ($storeId as $id) {
                        $storeIds[] = $id;
                    }
                    unset($storeIds[$key]);
                }
            }
        }
        return $storeIds;
    }

    /**
     * @param $inRows
     * @param $upRows
     * @return $this
     * @throws Exception
     */
    protected function saveProductsCache($inRows, $upRows)
    {
        $entityLinkField = $this->getProductEntityLinkField();
        if (!$this->originalImportRows) {
            $this->originalImportRows = $this->cache->load(sha1(ImportProductCache::BUFF_CACHE));
            $this->originalImportRows = $this->getSerializer()->unserialize($this->originalImportRows) ?? [];
        }

        foreach ($inRows as $row) {
            $id = $this->_oldSku[strtolower($row['sku'])]['entity_id'];
            foreach ($this->originalImportRows[strtolower($row['sku'])] as $item) {
                $this->cache->save(
                    1,
                    $item,
                    [ImportProductCache::CACHE_TAG . '_' . $id, ImportProductCache::CACHE_TAG]
                );
            }
        }

        $skuById = array_combine(array_column($this->_oldSku, 'entity_id'), array_keys($this->_oldSku));
        $productIdByRowId = [];
        if ($upRows) {
            $productIdByRowId = isset(current($upRows)['entity_id']) ? [] : $this->getProductIdByRowId($upRows);
        }
        foreach ($upRows as $row) {
            $id = $productIdByRowId ? $productIdByRowId[$row[$entityLinkField]] : $row['entity_id'];
            $sku = $skuById[$id];
            foreach ($this->originalImportRows[$sku] as $item) {
                $this->cache->save(
                    1,
                    $item,
                    [ImportProductCache::CACHE_TAG . '_' . $id, ImportProductCache::CACHE_TAG]
                );
            }
        }

        return $this;
    }

    /**
     * @param $rows
     * @return array
     */
    protected function getProductIdByRowId($rows)
    {
        $select = $this->_connection->select()->from(
            $this->getResource()->getTable('catalog_product_entity'),
            ['row_id', 'entity_id']
        )->where(
            'row_id IN(?)',
            array_column($rows, 'row_id')
        );

        return $this->getProductIdByRowId = $this->_connection->fetchPairs($select);
    }

    /**
     * @param $className
     * @return string
     */
    protected function getCurrentTaxClass($className)
    {
        if (!$this->classTaxNames) {
            $select = $this->_connection->select()->from(
                $this->getResource()->getTable('tax_class'),
                'class_name'
            )->where(
                'class_type = ?',
                ClassModel::TAX_CLASS_TYPE_PRODUCT
            );

            $result = [];
            foreach ($this->_connection->fetchCol($select) as $item) {
                $result [strtolower($item)] = $item;
            }
            $this->classTaxNames = $result;
        }

        $key = strtolower($className);
        return key_exists($key, $this->classTaxNames) ? $this->classTaxNames[$key] : $className;
    }

    /**
     * @param string $fileName
     * @param bool $renameFileOff
     * @param array $existingUpload
     *
     * @return string
     */
    protected function uploadMediaFiles($fileName, $renameFileOff = false, $existingUpload = [])
    {
        return $this->importImageProcessor->uploadMediaFiles($fileName, $renameFileOff, $existingUpload);
    }

    /**
     * @param $entityRowsIn
     * @throws Exception
     */
    protected function afterSaveNewEntities($entityRowsIn)
    {
        if ($entityRowsIn) {
            $entityTable = $this->_resourceFactory->create()->getEntityTable();

            $select = $this->_connection->select()->from(
                $entityTable,
                array_merge($this->getNewSkuFieldsForSelect(), $this->getOldSkuFieldsForSelect())
            )->where(
                $this->_connection->quoteInto('sku IN (?)', array_keys($entityRowsIn))
            );
            $newProducts = $this->_connection->fetchAll($select);
            foreach ($newProducts as $data) {
                $this->_optionEntity->addNewSkuToId($data[$this->getProductEntityLinkField()], $data['sku']);
            }
        }
    }

    /**
     * Return additional data, needed to select.
     * @return array
     */
    protected function getOldSkuFieldsForSelect()
    {
        return ['type_id', 'attribute_set_id'];
    }

    /**
     * Get product entity identifier field
     *
     * @return string
     * @throws Exception
     */
    protected function getProductIdentifierField()
    {
        if (!$this->productEntityIdentifierField) {
            $this->productEntityIdentifierField = $this->getMetadataPool()
                ->getMetadata(ProductInterface::class)
                ->getIdentifierField();
        }
        return $this->productEntityIdentifierField;
    }

    /**
     * Get new SKU fields for select
     *
     * @return array
     * @throws Exception
     */
    protected function getNewSkuFieldsForSelect()
    {
        $fields = ['sku', $this->getProductEntityLinkField()];
        if ($this->getProductEntityLinkField() != $this->getProductIdentifierField()) {
            $fields[] = $this->getProductIdentifierField();
        }
        return $fields;
    }

    /**
     * @param array $categoriesData
     * @param null $productId
     * @param bool $config
     *
     * @return $this|MagentoProduct
     */
    protected function _saveProductCategories(array $categoriesData, $productId = null, $config = false)
    {
        static $tableName = null;
        $removeCategories = $this->_parameters['remove_product_categories'] ?? 0;
        if ($removeCategories || $productId) {
            if (!$tableName) {
                $tableName = $this->_resourceFactory->create()->getProductCategoryTable();
            }
            if ($categoriesData) {
                $categoriesIn = [];
                $delProductId = [];

                foreach ($categoriesData as $productSku => $categories) {
                    if (!$config) {
                        $productId = $this->skuProcessor->getNewSku($productSku)['entity_id'];
                    }
                    $delProductId[] = $productId;
                    if ($this->onlyUpdate
                        && (int)$removeCategories > 0
                        && empty($categories)
                    ) {
                        $this->addLogWriteln(
                            __('Product %1 Categories Cannot be Cleared', $productSku),
                            $this->output,
                            'info'
                        );
                        continue;
                    }
                    $cat = [];
                    foreach ($categories as $category => $delete) {
                        if ($delete === true) {
                            $cat[$category] = true;
                        }
                    }

                    foreach (array_keys($cat) as $categoryId) {
                        $position = 1;
                        try {
                            $positions = $this->getCategoryPosition($categoryId);
                            $existingPositions = [];
                            foreach ($positions as $position) {
                                $existingPositions[] = $position['position'];
                            }
                            if (!in_array(1, $existingPositions)) {
                                $position = 1;
                            } elseif ($maxPos = max($existingPositions)) {
                                $position = $maxPos + 1;
                            }
                        } catch (Exception $exception) {
                            $position = 1;
                        }
                        $categoriesIn[] = [
                            'product_id' => $productId,
                            'category_id' => $categoryId,
                            'position' => $position
                        ];
                    }
                }
                if ($removeCategories || $config) {
                    $this->_connection->delete(
                        $tableName,
                        $this->_connection->quoteInto('product_id IN (?)', $delProductId)
                    );
                }
                if ($categoriesIn) {
                    $this->_connection->insertOnDuplicate($tableName, $categoriesIn, ['product_id', 'category_id']);
                }
            }
            return $this;
        }
        return parent::_saveProductCategories($categoriesData);
    }

    /**
     * @param array $categoryProductPosition
     * @return $this
     */
    protected function _saveProductCategoriesPosition(array $categoryProductPosition)
    {
        static $tableName = null;

        if (!$tableName) {
            $tableName = $this->_resource->getTable('catalog_category_product');
        }

        $positionsIn = [];
        foreach ($categoryProductPosition as $sku => $categories) {
            $productId = $this->skuProcessor->getNewSku($sku)['entity_id'];
            foreach ($categories as $categoryId => $position) {
                $positionsIn[] = ['product_id' => $productId, 'category_id' => $categoryId, 'position' => $position];
            }
        }

        if ($positionsIn) {
            $this->_connection->insertOnDuplicate($tableName, $positionsIn, ['position']);
        }

        return $this;
    }

    /**
     * @param array $attributeList
     * @param $sku
     * @param int $rowStoreId
     * @return array|false
     * @throws Exception
     */
    protected function getDefaultAttributesValue(array $attributeList, $sku, $rowStoreId = 0)
    {
        $resultList = [];
        $defaultStoreId = 0;
        $storeIdList = [];
        $storeIdList[] = $defaultStoreId;
        if ($rowStoreId != $defaultStoreId) {
            $storeIdList[] = $rowStoreId;
        }

        $linkField = $this->getProductEntityLinkField();

        if (!isset($this->_oldSku[$sku][$linkField])) {
            return false;
        }
        $linksFieldId = $this->_oldSku[$sku][$linkField];

        $attributeIdListByGroup = [];
        foreach ($attributeList as $attribute) {
            $attributeIdListByGroup[$attribute->getBackend()->getTable()][] = $attribute->getId();
        }

        foreach ($attributeIdListByGroup as $table => $attrIdList) {
            $select = $this->_connection->select()
                ->from($table, ['*'])
                ->where('attribute_id in(?)', $attrIdList)
                ->where('store_id in(?)', $storeIdList)
                ->where($linkField . ' = ?', $linksFieldId);

            $attrs = $this->_connection->fetchAll($select);
            if (!empty($attrs)) {
                foreach ($attrs as $attr) {
                    $resultKey = $this->getAttributeDefaultValueKey($sku, $attr['attribute_id'], $attr['store_id']);
                    $resultList[$resultKey] = $attr['value'];
                }
            }
        }

        return $resultList;
    }

    /**
     * @param $sku
     * @param $attributeId
     * @param int $storeId
     * @return string
     */
    protected function getAttributeDefaultValueKey($sku, $attributeId, $storeId = 0)
    {
        return implode('-', [$sku, $attributeId, $storeId]);
    }

    /**
     * @param AbstractAttribute $attribute
     * @param string $sku
     * @param int $storeId
     * @return bool|string
     * @throws Exception
     */
    protected function _deleteStoreAttributeValue(AbstractAttribute $attribute, $sku, $storeId = 0)
    {
        if (!isset($this->_oldSku[strtolower($sku)])) {
            return false;
        }

        $linkField = $this->getProductEntityLinkField();
        $linkId = $this->_oldSku[strtolower($sku)][$linkField];

        $deleteCondition[] = $this->_connection->quoteInto(
            'attribute_id = ?',
            $attribute->getId()
        );
        $deleteCondition[] = $this->_connection->quoteInto(
            'store_id = ?',
            $storeId
        );
        $deleteCondition[] = $this->_connection->quoteInto(
            $linkField . ' = ?',
            $linkId
        );

        $deleteCondition = implode(' AND ', $deleteCondition);

        return $this->_connection->delete($attribute->getBackend()->getTable(), $deleteCondition);
    }

    /**
     * Init media gallery resources.
     *
     * @return void
     */
    public function initMediaGalleryResources()
    {
        if (null == $this->mediaGalleryTableName) {
            $this->productEntityTableName = $this->getResource()->getTable('catalog_product_entity');
            $this->mediaGalleryTableName = $this->getResource()->getTable('catalog_product_entity_media_gallery');
            $this->mediaGalleryValueTableName = $this->getResource()->getTable(
                'catalog_product_entity_media_gallery_value'
            );
            $this->mediaGalleryEntityToValueTableName = $this->getResource()->getTable(
                'catalog_product_entity_media_gallery_value_to_entity'
            );
        }
    }

    /**
     * @param $newMediaValues
     * @return $this
     */
    protected function removeExistingImages($newMediaValues)
    {
        $this->importImageProcessor->removeExistingImages($newMediaValues);
        return $this;
    }

    /**
     * @param string $sku
     * @return $this
     */
    protected function removeRelatedProducts($sku)
    {
        if (!isset($this->_oldSku[strtolower($sku)])) {
            return $this;
        }
        try {
            $entityLinkField = $this->getProductEntityLinkField();
            $productId = $this->_oldSku[strtolower($sku)][$entityLinkField];
            $linkTypeId = ProductLink::LINK_TYPE_RELATED;
            $linkTable = $this->getResource()->getTable('catalog_product_link');
            $this->_connection->delete(
                $linkTable,
                ['product_id=' . $productId, 'link_type_id=' . $linkTypeId]
            );
        } catch (Exception $e) {
            $this->addLogWriteln($e->getMessage(), $this->output, 'error');
        }

        return $this;
    }

    /**
     * @param $sku
     *
     * @return $this
     */
    protected function removeCrosssellProducts($sku)
    {
        if (!isset($this->_oldSku[strtolower($sku)])) {
            return $this;
        }
        try {
            $entityLinkField = $this->getProductEntityLinkField();
            $productId = $this->_oldSku[strtolower($sku)][$entityLinkField];
            $linkTypeId = ProductLink::LINK_TYPE_CROSSSELL;
            $linkTable = $this->getResource()->getTable('catalog_product_link');
            $this->_connection->delete(
                $linkTable,
                ['product_id=' . $productId, 'link_type_id=' . $linkTypeId]
            );
        } catch (Exception $e) {
            $this->addLogWriteln($e->getMessage(), $this->output, 'error');
        }

        return $this;
    }

    /**
     * @param $sku
     *
     * @return $this
     */
    protected function removeUpsellProducts($sku)
    {
        if (!isset($this->_oldSku[strtolower($sku)])) {
            return $this;
        }
        try {
            $entityLinkField = $this->getProductEntityLinkField();
            $productId = $this->_oldSku[strtolower($sku)][$entityLinkField];
            $linkTypeId = ProductLink::LINK_TYPE_UPSELL;
            $linkTable = $this->getResource()->getTable('catalog_product_link');
            $this->_connection->delete(
                $linkTable,
                ['product_id=' . $productId, 'link_type_id=' . $linkTypeId]
            );
        } catch (Exception $e) {
            $this->addLogWriteln($e->getMessage(), $this->output, 'error');
        }

        return $this;
    }

    /**
     * Initialize source type model
     *
     * @param $type
     *
     * @throws LocalizedException
     */
    protected function _initSourceType($type)
    {
        if (!$this->sourceType) {
            $this->sourceType = $this->additional->getSourceModelByType($type);
            $this->sourceType->setData($this->_parameters);
        }
    }

    /**
     * Import images via initialized source type
     *
     * @param $bunch
     *
     * @return mixed
     */
    protected function prepareImagesFromSource($bunch)
    {
        foreach ($bunch as $rowNum => &$rowData) {
            $rowData = $this->customFieldsMapping($rowData);
            $this->addLogWriteln(
                __('Downloading Image From Source for product sku %1', $this->getCorrectSkuAsPerLength($rowData)),
                $this->getOutput(),
                'info'
            );
            foreach ($this->_imagesArrayKeys as $image) {
                if (empty($rowData[$image])) {
                    continue;
                }
                $dispersionPath = \Magento\Framework\File\Uploader::getDispretionPath($rowData[$image]);
                $importImages = explode($this->getMultipleValueSeparator(), $rowData[$image]);
                $imageArr = [];
                foreach ($importImages as $importImage) {
                    $imageSting = mb_strtolower(
                        $dispersionPath . '/' . preg_replace('/[^a-z0-9\._-]+/i', '', $importImage)
                    );
                    try {
                        if ($this->sourceType) {
                            if ($this->sourceType->getCode() === 'rest') {
                                $sourceImport = $this->sourceType->importImage($importImage, $imageSting);
                                $imageArr[] = $this->sourceType->getCode() . $sourceImport[1];
                            } else {
                                $this->sourceType->importImage($importImage, $imageSting);
                                $imageArr[] = $this->sourceType->getCode() . $imageSting;
                            }
                        }
                    } catch (\Exception $e) {
                        $this->getLogger()->warning($e->getMessage());
                        $this->addLogWriteln($e->getMessage(), $this->output, 'warning');
                    }
                }
                $rowData[$image] = implode($this->getMultipleValueSeparator(), $imageArr);
            }
        }

        return $bunch;
    }

    /**
     * @param array $rowData
     * @param null $storeIds
     * @return array
     * @throws Exception
     */
    protected function generateUrlKey(array $rowData, $storeIds = null)
    {
        $productEntityLinkField = $this->getProductEntityLinkField();
        $sku = $this->getCorrectSkuAsPerLength($rowData);
        $urlKey = $rowData[self::URL_KEY] ?? '';
        $name = $rowData[self::COL_NAME] ?? '';
        if ($this->isSkuExist($sku)) {
            $exiting = $this->getExistingSku($sku);
            if (!$urlKey) {
                $attr = $this->retrieveAttributeByCode(self::URL_KEY);
                $select = $this->getConnection()->select()
                    ->from($attr->getBackendTable(), ['value'])
                    ->where($productEntityLinkField . ' = (?)', $exiting['entity_id'])
                    ->where('attribute_id = (?)', $attr->getAttributeId());
                $urlKey = $this->getConnection()->fetchOne($select);
            }
            if (!$name) {
                $attr = $this->retrieveAttributeByCode(self::COL_NAME);
                $select = $this->getConnection()->select()
                    ->from($attr->getBackendTable(), ['value'])
                    ->where($productEntityLinkField . ' = (?)', $exiting['entity_id'])
                    ->where('attribute_id = (?)', $attr->getAttributeId());
                $name = $this->getConnection()->fetchOne($select);
                if (!$urlKey) {
                    $urlKey = $name;
                }
            }
        } else {
            $urlKey = isset($rowData[self::URL_KEY])
                ? $urlKey
                : $name;
        }
        if ($storeIds === null) {
            $storeIds = $this->getStoreIds();
        }
        $urlKey = ($urlKey != '') ?
            $this->productUrl->formatUrlKey($urlKey)
            : $this->productUrl->formatUrlKey($name);
        $isDuplicate = $this->isDuplicateUrlKey($urlKey, $sku, $storeIds);
        $storeId = $this->getRowStoreId($rowData);
        if ($isDuplicate || $this->urlKeyManager->isUrlKeyExist($sku, $urlKey, $storeId)) {
            $urlKey = $this->productUrl->formatUrlKey(
                $name . '-' . $sku
            );
        }
        $rowData[self::URL_KEY] = $urlKey;
        $this->urlKeyManager->addUrlKeys($sku, $urlKey, $storeId);
        return $rowData;
    }

    /**
     * Custom fields mapping for changed purposes of fields and field names.
     *
     * @param array $rowData
     *
     * @return array
     */
    public function customFieldsMapping($rowData)
    {
        $rowData = $this->attributeValuesMapping($rowData);
        if (!empty($this->_parameters['use_only_fields_from_mapping'])) {
            $rowData = $this->useOnlyFieldsFromMapping($rowData);
        }
        foreach ($this->_fieldsMap as $systemFieldName => $fileFieldName) {
            if (array_key_exists($fileFieldName, $rowData) && !isset($rowData[$systemFieldName])) {
                $rowData[$systemFieldName] = $rowData[$fileFieldName];
            }
        }
        // restore data for configurable field when it is already used in Map Attributes section
        $configField = $this->_parameters['configurable_field'];
        if ($configField && !isset($rowData[$configField])) {
            $configKey = array_search($configField, $this->_fieldsMap);
            if ($configKey !== false) {
                $rowData[$configField] = $rowData[$configKey];
            }
        }
        //
        $rowData = $this->_parseAdditionalAttributes($rowData);
        $rowData = $this->setStockUseConfigFieldsValues($rowData);

        if (array_key_exists('status', $rowData)
            && $rowData['status'] != Status::STATUS_ENABLED
        ) {
            if ($rowData['status'] == 'yes') {
                $rowData['status'] = Status::STATUS_ENABLED;
            } elseif (!empty($rowData['status']) || $this->getRowScope($rowData) == self::SCOPE_DEFAULT) {
                $rowData['status'] = Status::STATUS_DISABLED;
            }
        }
        $imageImportPath = $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR] ?? '';
        if (preg_match('/\bhttps?:\/\//i', $imageImportPath, $matches)) {
            foreach ($this->_imagesArrayKeys as $image) {
                if (empty($rowData[$image]) && !isset($rowData[$image])) {
                    continue;
                }
                if (strpos($rowData[$image], $imageImportPath) !== false) {
                    continue;
                }
                if ($image === 'additional_images') {
                    $addImage = [];
                    foreach (explode($this->getMultipleValueSeparator(), $rowData[$image]) as $rowImage) {
                        $addImage[] = $imageImportPath . '/' . $rowImage;
                    }
                    $rowData[$image] = implode($this->getMultipleValueSeparator(), $addImage);
                } else {
                    $rowData[$image] = $imageImportPath . '/' . $rowData[$image];
                }
            }
        }

        foreach ($this->_imagesArrayKeys as $image) {
            if ($image != '_media_image') {
                if (isset($rowData[$image])) {
                    $rowData[$image] = trim($rowData[$image]);
                }
            }
        }

        return $rowData;
    }

    /**
     * Parse attributes names and values string to array.
     *
     * @param array $rowData
     *
     * @return array
     */
    protected function _parseAdditionalAttributes($rowData)
    {
        if (empty($rowData['additional_attributes'])) {
            return $rowData;
        }
        try {
            $source = $this->_getSource();
        } catch (Exception $e) {
            $source = null;
        }
        $valuePairs = explode(
            $this->getMultipleValueSeparator(),
            $rowData['additional_attributes']
        );
        foreach ($valuePairs as $valuePair) {
            $separatorPosition = strpos($valuePair, self::PAIR_NAME_VALUE_SEPARATOR);
            if ($separatorPosition !== false) {
                $key = substr($valuePair, 0, $separatorPosition);
                $value = substr(
                    $valuePair,
                    $separatorPosition + strlen(self::PAIR_NAME_VALUE_SEPARATOR)
                );
                if ($source !== null) {
                    $key = $source->changeField($key);
                }
                $multiLineSeparator = strpos($value, self::PSEUDO_MULTI_LINE_SEPARATOR);
                if ($multiLineSeparator !== false) {
                    $attribute = $this->retrieveAttributeByCode($key);
                    if ($attribute
                        && (($attribute->getBackendType() !== 'text'
                        && !$this->verifySwatchString($value))
                        || $attribute->getFrontendInput() == 'multiselect')
                    ) {
                        $value = implode(
                            $this->getMultipleValueSeparator(),
                            explode(
                                self::PSEUDO_MULTI_LINE_SEPARATOR,
                                $value
                            )
                        );
                    }
                }
                $rowData[$key] = $value === false ? '' : $value;
            }
        }
        unset($rowData['additional_attributes']);
        return $rowData;
    }

    /**
     * @param string $value
     * @return bool
     */
    protected function verifySwatchString(string $value)
    {
        return (strpos($value, 'type=') !== false && strpos($value, 'value=') !== false)
            ? true
            : false;
    }

    /**
     * Set values in use_config_ fields.
     *
     * @param array $rowData
     * @return array
     */
    protected function setStockUseConfigFieldsValues($rowData)
    {
        foreach ($rowData as $field => $value) {
            $useConfigField = self::INVENTORY_USE_CONFIG_PREFIX . $field;
            if (isset($this->defaultStockData[$field])
                && isset($this->defaultStockData[$useConfigField])
                && $value == self::INVENTORY_USE_CONFIG
            ) {
                $rowData[$useConfigField] = 1;
            }
        }
        return $rowData;
    }

    /**
     * Replace attribute values according map
     *
     * @param array $rowData
     *
     * @return array
     * @throws NoSuchEntityException
     */
    protected function attributeValuesMapping($rowData)
    {
        if (empty($this->jobsCache[$this->_parameters['job_id']])) {
            $this->jobsCache[$this->_parameters['job_id']] =
                $this->jobRepository->getById($this->_parameters['job_id']);
        }
        $job = $this->jobsCache[$this->_parameters['job_id']];
        $map = $this->phpUnserialize($job->getMapping());

        foreach ($map as $item) {
            if (isset($item['source_data_attribute_value_system']) &&
                isset($item['source_data_attribute_value_import'])
            ) {
                foreach ($rowData as $key => $value) {
                    if (!is_array($value) && (trim($value) == trim($item['source_data_attribute_value_import']))) {
                        $rowData[$key] = $item['source_data_attribute_value_system'];
                    }
                }
            }
        }

        return $rowData;
    }

    /**
     * @param $rowData
     * @return array
     */
    protected function categoriesMapping($rowData)
    {
        $mappedCategories = [];
        $mappedCategoriesPosition = [];

        $categories_separator = $this->_parameters['categories_separator'];
        $explodeImportedCategoriesItems = explode($categories_separator, $rowData[self::COL_CATEGORY]);

        $explodeImportedCategoriesPositionItems = [];
        $importedCategoriesPositionItems = (!empty($rowData[self::COL_CATEGORY . '_position']))
            ? explode($categories_separator, $rowData[self::COL_CATEGORY . '_position'])
            : [];
        foreach ($importedCategoriesPositionItems as $index => $positionItem) {
            if (empty($positionItem)) {
                continue;
            }
            $categoriesPositionValue = explode(self::PAIR_NAME_VALUE_SEPARATOR, $positionItem);
            $explodeImportedCategoriesPositionItems[$categoriesPositionValue[0]] = [
                $index,
                $categoriesPositionValue[1] ?? 0
            ];
        }

        $connection = $this->_connection;
        $resource = $this->getResource();
        $select = $connection->select()->from(
            [
                'main' => $resource->getTable('firebear_import_jobs'),
            ],
            ['mapping']
        )->where('entity_id=?', $this->_parameters['job_id']);
        $maps = $this->_connection->fetchAll(
            $select
        );
        foreach ($maps as $map) {
            $newCategoriesMapItems = $this->phpUnserialize($map['mapping']);
            foreach ($newCategoriesMapItems as $newCategoriesMapItem) {
                foreach ($explodeImportedCategoriesItems as $key => &$explodeImportedCategoriesItem) {
                    if (isset($newCategoriesMapItem['source_category_data_import']) &&
                        trim($explodeImportedCategoriesItem) == $newCategoriesMapItem['source_category_data_import']
                    ) {
                        $newCategoriesMapItemId = $newCategoriesMapItem['source_category_data_new_id'];
                        $mappedCategories[] = $newCategoriesMapItemId;
                        unset($explodeImportedCategoriesItems[$key]);
                        $this->setIsRowCategoryMapped(true);

                        if (isset($explodeImportedCategoriesPositionItems[$explodeImportedCategoriesItem])) {
                            $index = $explodeImportedCategoriesPositionItems[$explodeImportedCategoriesItem][0];
                            $position = $explodeImportedCategoriesPositionItems[$explodeImportedCategoriesItem][1];
                            unset($importedCategoriesPositionItems[$index]);
                            $mappedCategoriesPosition[$newCategoriesMapItemId] = $position;
                        }
                    }
                }
            }
        }
        return [
            'mappedCategories' => $mappedCategories,
            'mappedCategoriesPosition' => $mappedCategoriesPosition,
            self::COL_CATEGORY => implode($categories_separator, $explodeImportedCategoriesItems),
            self::COL_CATEGORY . '_position' => implode($categories_separator, $importedCategoriesPositionItems)
        ];
    }

    /**
     * Multiple value separator getter
     *
     * @return string
     */
    public function getMultipleValueSeparator()
    {
        return $this->separatorFormatter->format(
            parent::getMultipleValueSeparator()
        );
    }

    /**
     * @param MagentoProduct\Type\AbstractType $productTypeModel
     * @param array $rowData
     * @return array
     * @throws LocalizedException
     */
    public function createAttributeValues(
        MagentoProduct\Type\AbstractType $productTypeModel,
        array $rowData
    ) {
        $options = [];
        if (isset($rowData[self::COL_ATTR_SET])) {
            $attributeSet = $rowData[self::COL_ATTR_SET];
            foreach ($rowData as $attrCode => $attrValue) {
                /**
                 * Add attribute to set & set's group
                 */
                if (preg_match('/^(attribute\|).+/', $attrCode)) {
                    $columnData = explode('|', $attrCode);
                    $columnData = $this->prepareAttributeData($columnData);
                    // might be already inside additional_attributes
                    if (isset($rowData[$columnData['attribute_code']])) {
                        unset($rowData[$attrCode]);
                        continue;
                    } else {
                        $rowData[$columnData['attribute_code']] = $rowData[$attrCode];
                        unset($rowData[$attrCode]);
                        $attrCode = $columnData['attribute_code'];
                    }
                }

                /**
                 * Prepare new values
                 */
                $attrParams = $productTypeModel->retrieveAttribute($attrCode, $attributeSet);
                if (!empty($attrParams)) {
                    if (!$attrParams['is_static'] &&
                        isset($rowData[$attrCode]) &&
                        trim((string)$rowData[$attrCode]) !== ''
                    ) {
                        $empty = $this->_parameters['_import_empty_attribute_value_constant'] ?? null;
                        if (($attrParams['type'] == 'select' || $attrParams['type'] == 'multiselect')
                            && trim($rowData[$attrCode]) == $empty) {
                            continue;
                        }

                        switch ($attrParams['type']) {
                            case 'select':
                                $swatchOptionData = [];
                                $swatchOptions = [];
                                /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute\Interceptor $attribute */
                                $attribute = $this->retrieveAttributeByCode($attrCode);
                                if ($this->swatchesHelperData->isVisualSwatch($attribute)) {
                                    $swatchOptionData = $this->prepareSwatchOptionData($rowData[$attrCode]);
                                    $swatchOptions = $this->getSwatchesByOptionsId(
                                        $attrParams['options'],
                                        $attrParams['id']
                                    );
                                }
                                if ($this->swatchesHelperData->isTextSwatch($attribute)) {
                                    $swatchOptionData = $rowData[$attrCode];
                                    $swatchOptions = $this->getSwatchesByOptionsId(
                                        $attrParams['options'],
                                        $attrParams['id']
                                    );
                                }
                                if ((isset($attrParams['additional_data']['update_product_preview_image'])
                                        || isset($attrParams['additional_data']['use_product_image_for_swatch']))
                                    && $this->verifySwatchString($rowData[$attrCode])
                                ) {
                                    $swatchOptionData = $rowData[$attrCode];
                                    $swatchOptions = $this->getSwatchesByOptionsId(
                                        $attrParams['options'],
                                        $attrParams['id']
                                    );
                                }
                                $lowerAttrValue = strtolower($rowData[$attrCode]);
                                // no attribute option
                                $storeCode = isset($rowData[self::COL_STORE_VIEW_CODE])
                                    ? $rowData[self::COL_STORE_VIEW_CODE] : false;
                                $scopeStore = self::SCOPE_STORE == $this->getRowScope($rowData);
                                $attrOptions = $attrParams['options'];
                                if ($scopeStore && $storeCode && !empty($attrParams['options_store'][$storeCode])) {
                                    if (isset($attrOptions[$lowerAttrValue]) &&
                                        !isset($attrParams['options_store'][$storeCode][$lowerAttrValue])
                                    ) {
                                        $attrParams['options_store'][$storeCode][$lowerAttrValue] =
                                            $attrOptions[$lowerAttrValue];
                                    }
                                    $attrOptions = $attrParams['options_store'][$storeCode];
                                }

                                if (!isset($attrOptions[$lowerAttrValue])) {
                                    $options[$attrParams['id']][] = [
                                        'sort_order' => count($attrParams['options']) + 1,
                                        'value' => $rowData[$attrCode],
                                        'code' => $attrCode,
                                        'swatch_option' => $swatchOptionData,
                                    ];
                                } elseif (!empty($swatchOptionData) &&
                                    !array_key_exists($attrOptions[$lowerAttrValue], $swatchOptions)
                                ) { // no attribute swatch option
                                    $newSwatchOptions[$attrParams['id']][$attrOptions[$lowerAttrValue]] =
                                        $swatchOptionData;
                                } elseif (array_key_exists($attrOptions[$lowerAttrValue], $swatchOptions)) {
                                    // swatch attribute option exist
                                    $swatchOption = $swatchOptions[$attrOptions[$lowerAttrValue]];
                                    if ($this->swatchesHelperData->isVisualSwatch($attribute)) {
                                        // but has different value or type
                                        if (isset($attrParams['additional_data']['update_product_preview_image'])
                                            || isset($attrParams['additional_data']['use_product_image_for_swatch'])
                                            && !is_array($swatchOptionData)
                                        ) {
                                            break; //continue 2
                                        }
                                        if (!empty($diff = array_diff_assoc($swatchOptionData, $swatchOption))) {
                                            if ((array_key_exists('type', $diff) &&
                                                    $diff['type'] == Swatch::SWATCH_TYPE_VISUAL_COLOR)
                                                || (!array_key_exists('type', $diff) &&
                                                    $swatchOption['type'] == Swatch::SWATCH_TYPE_VISUAL_COLOR)
                                            ) {
                                                $this->updateSwatchOption($swatchOption, $diff);
                                            } elseif ($this->ifVisualSwatchOptionDifferent($swatchOption, $diff)) {
                                                $diff['value'] = $this->uploadVisualSwatchFile($diff['value']);
                                                $this->updateSwatchOption($swatchOption, $diff);
                                            }
                                        }
                                    }
                                }
                                break;
                            case 'multiselect':
                                $separator = $this->_parameters['_import_multiple_value_separator'] ?
                                    $this->_parameters['_import_multiple_value_separator'] :
                                    MagentoProduct::PSEUDO_MULTI_LINE_SEPARATOR;
                                $separator = $this->separatorFormatter->format($separator);
                                $values = explode($separator, $rowData[$attrCode]);
                                foreach ($values as $value) {
                                    $value = trim($value);
                                    if (!isset($attrParams['options'][strtolower($value)])) {
                                        $options[$attrParams['id']][] = [
                                            'sort_order' => count($attrParams['options']) + 1,
                                            'value' => $value,
                                            'code' => $attrCode,
                                        ];
                                    }
                                }
                                break;
                            default:
                                break;
                        }
                    }
                }
            }

            /**
             * Create new values
             */
            if (!empty($options)) {
                $connection = $this->_connection;
                $resource = $this->getResource();
                foreach ($options as $attributeId => $optionsArray) {
                    foreach ($optionsArray as $option) {
                        /**
                         * @see \Magento\Eav\Model\ResourceModel\Entity\Attribute::_updateAttributeOption()
                         */
                        $table = $resource->getTable('eav_attribute_option');
                        $data = ['attribute_id' => $attributeId, 'sort_order' => $option['sort_order']];
                        $connection->insert($table, $data);
                        $intOptionId = $connection->lastInsertId($table);
                        /**
                         * @see \Magento\Eav\Model\ResourceModel\Entity\Attribute::_updateAttributeOptionValues()
                         */
                        $table = $resource->getTable('eav_attribute_option_value');
                        $data = [
                            'option_id' => $intOptionId,
                            'store_id' => Store::DEFAULT_STORE_ID,
                            'value' => $option['value']
                        ];
                        $connection->insert($table, $data);
                        if (isset($option['swatch_option']) && !empty($option['swatch_option'])) {
                            $this->insertNewSwatchOption(
                                $connection,
                                $resource,
                                $intOptionId,
                                $option['swatch_option'],
                                $attributeId
                            );
                        }
                        foreach ($this->_productTypeModels as $productTypeModel) {
                            $productTypeModel->addAttributeOption(
                                $option['code'],
                                strtolower($option['value']),
                                $intOptionId
                            );
                        }
                    }
                }
            }
            if (!empty($newSwatchOptions)) {
                $connection = $this->_connection;
                $resource = $this->getResource();
                foreach ($newSwatchOptions as $attributeId => $swatchOption) {
                    foreach ($swatchOption as $optionId => $swatchData) {
                        $this->insertNewSwatchOption($connection, $resource, $optionId, $swatchData, $attributeId);
                    }
                }
            }
        }

        return $rowData;
    }

    /**
     * Convert attribute string syntax to array.
     *
     * @param $columnData
     *
     * @return array
     * @throws Exception
     */
    protected function prepareAttributeData($columnData)
    {
        $result = [];
        foreach ($columnData as $field) {
            $field = explode(':', $field);
            if (isset($field[1])) {
                if (preg_match('/^(frontend_label_)[0-9]+/', $field[0])) {
                    $result['frontend_label'][(int)substr($field[0], -1)] = $field[1];
                } else {
                    $result[$field[0]] = $field[1];
                }
            }
        }

        if (!empty($result)) {
            $attributeCode = isset($result['attribute_code']) ? $result['attribute_code'] : null;
            $frontendLabel = $result['frontend_label'][0];
            $attributeCode = $attributeCode ?: $this->generateAttributeCode($frontendLabel);
            $result['attribute_code'] = $attributeCode;

            $entityTypeId = $this->eavEntityFactory->create()->setType(
                \Magento\Catalog\Model\Product::ENTITY
            )->getTypeId();
            $result['entity_type_id'] = $entityTypeId;
            $result['is_user_defined'] = 1;
        }

        return $result;
    }

    /**
     * Generate code from label
     *
     * @param $label
     * @return false|string
     */
    protected function generateAttributeCode($label)
    {
        $code = substr(
            preg_replace(
                '/[^a-z_0-9]/',
                '_',
                $this->productUrl->formatUrlKey($label)
            ),
            0,
            30
        );

        $validatorAttrCode = new Regex(['pattern' => '/^[a-z][a-z_0-9]{0,29}[a-z0-9]$/']);

        if (!$validatorAttrCode->isValid($code)) {
            $code = 'attr_' . ($code ?: substr(hash("md5", time()), 0, 8));
        }

        return $code;
    }

    /**
     * Parse swatch attribute value, pulls actual attribute value and swatch options if they are there.
     *
     * @param $attributeValue string Swatch attribute value in follow format
     *     "{value}|type={1,2}|value={#FFFFFF,path/to/image.file}" where: type:1 - color type:2 - image
     *
     * @return array
     */
    protected function prepareSwatchOptionData(&$attributeValue)
    {
        $swatchOptionData = [];
        $preParsedData = explode('|', $attributeValue);
        if (count($preParsedData) > 1) {
            foreach ($preParsedData as $key => $value) {
                if ($key == 0) {
                    $attributeValue = $value; //set attributes value
                    continue;
                }
                $value = explode('=', $value);
                if (isset($value[1])) {
                    $swatchOptionData[$value[0]] = $value[1];
                }
            }
        } else {
            $swatchOptionData['type'] = Swatch::SWATCH_TYPE_VISUAL_COLOR;
            $swatchOptionData['value'] = $attributeValue;
        }

        return $swatchOptionData;
    }

    /**
     * Returns Swatch option data for Attribute Option Ids
     *
     * @param array $optionIds
     * @param int $attributeId
     *
     * @return array
     */
    protected function getSwatchesByOptionsId($optionIds, $attributeId)
    {
        if (!isset($this->cachedSwatchOptions[$attributeId]) || empty($this->cachedSwatchOptions[$attributeId])) {
            $this->cachedSwatchOptions[$attributeId] = [];
            $swatchCollection = $this->swatchCollectionFactory->create();
            $swatchCollection->addFilterByOptionsIds($optionIds);
            foreach ($swatchCollection as $item) {
                $this->cachedSwatchOptions[$attributeId][$item['option_id']] = $item->getData();
            }
        }

        return $this->cachedSwatchOptions[$attributeId];
    }

    /**
     * @param int $swatchOption
     * @param array $diff
     */
    protected function updateSwatchOption($swatchOption, $diff)
    {
        $connection = $this->_connection;
        $resource = $this->getResource();
        $table = $resource->getTable('eav_attribute_option_swatch');
        if (isset($swatchOption['swatch_id'])) {
            $where = ['swatch_id=?' => (int)$swatchOption['swatch_id']];
            $connection->update($table, $diff, $where);
        }
    }

    /**
     * Checks if imported image for swatch option is different then exist one.
     *
     * @param int $swatchOption
     * @param array $diff Array of type and value that are different
     *
     * @return bool
     * @throws LocalizedException
     */
    protected function ifVisualSwatchOptionDifferent($swatchOption, $diff)
    {
        // TODO: need implement logic for unique names -
        // sometimes image name might have _1_2 endings for the same image.
        if (isset($diff['value'])) {
            $fileName = preg_replace('/[^a-z0-9\._-]+/i', '', $diff['value']);
            $dispretionPath = $this->_getUploader()->getDispretionPath($fileName);
            return !($swatchOption['value'] == $dispretionPath . '/' . $fileName);
        }
        return false;
    }

    /**
     * @return \Magento\CatalogImportExport\Model\Import\Uploader|Uploader
     * @throws LocalizedException
     */
    protected function _getUploader()
    {
        $DS = DIRECTORY_SEPARATOR;
        if ($this->_fileUploader === null) {
            $this->_fileUploader = $this->_uploaderFactory->create();
            $this->_fileUploader->init();
            $dirConfig = DirectoryList::getDefaultConfig();
            $dirAddon = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH];
            if (!empty($this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR])) {
                $tmpPath = $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR];
            } else {
                $tmpPath = $dirAddon . $DS . $this->_mediaDirectory->getRelativePath('import');
            }
            if (preg_match('/\bhttps?:\/\//i', $tmpPath, $matches)) {
                $tmpPath = $dirAddon . $DS . $this->_mediaDirectory->getRelativePath('import');
            }
            if (!$this->_fileUploader->setTmpDir($tmpPath)) {
                $this->addLogWriteln(__('File directory \'%1\' is not readable.', $tmpPath), $this->output, 'info');
                $this->addRowError(
                    __('File directory \'%1\' is not readable.', $tmpPath),
                    null,
                    null,
                    null,
                    ProcessingError::ERROR_LEVEL_NOT_CRITICAL
                );
                throw new LocalizedException(
                    __('File directory \'%1\' is not readable.', $tmpPath)
                );
            }
            $destinationDir = "catalog/product";
            $destinationPath = $dirAddon . $DS . $this->_mediaDirectory->getRelativePath($destinationDir);

            $this->_mediaDirectory->create($destinationPath);
            if (!$this->_fileUploader->setDestDir($destinationPath)) {
                $this->addRowError(
                    __('File directory \'%1\' is not writable.', $destinationPath),
                    null,
                    null,
                    null,
                    ProcessingError::ERROR_LEVEL_NOT_CRITICAL
                );
                throw new LocalizedException(
                    __('File directory \'%1\' is not writable.', $destinationPath)
                );
            }
        }

        if ($this->_fileUploader) {
            $this->_fileUploader->setEntity($this);
            $this->_fileUploader->setOutput($this->getOutput());
        }

        return $this->_fileUploader;
    }

    /**
     * Uploads Image for Image Swatch option
     *
     * @param string $swatchVisualFile
     * @return string
     * @throws LocalizedException
     */
    protected function uploadVisualSwatchFile($swatchVisualFile)
    {
        $config = $this->mediaConfig;
        $uploader = $this->_getUploader();
        $newFile = '';
        $dirConfig = DirectoryList::getDefaultConfig();
        $mediaRelativePath = $dirConfig[DirectoryList::MEDIA][DirectoryList::PATH];
        try {
            $destDir = $uploader->getDestDir();
            $uploadDir = $mediaRelativePath . DIRECTORY_SEPARATOR . $config->getBaseTmpMediaPath();
            $uploadDir = $this->_mediaDirectory->getAbsolutePath($uploadDir);

            if (!$uploader->isDirectoryWritable($uploadDir)) {
                $uploader->createDirectory($uploadDir);
            }
            if (!$uploader->setDestDir($uploadDir)) {
                $this->addRowError(
                    __('File directory \'%1\' is not writable.', $config->getBaseTmpMediaPath()),
                    null,
                    null,
                    null,
                    ProcessingError::ERROR_LEVEL_NOT_CRITICAL
                );
                throw new LocalizedException(
                    __('File directory \'%1\' is not writable.', $config->getBaseTmpMediaPath())
                );
            } else {
                $result = $uploader->move($swatchVisualFile);
                $newFile = $this->swatchHelperMedia->moveImageFromTmp($result['file']);
                $this->swatchHelperMedia->generateSwatchVariations($newFile);
                $uploader->setDestDir($destDir);
            }
        } catch (Exception $e) {
            $this->addLogWriteln($e->getMessage(), $this->output, 'error');
        }

        return $newFile;
    }

    /**
     * @param AdapterInterface $connection
     * @param ResourceModel $resource
     * @param int $optionId
     * @param array $swatchData
     * @param int $attributeId
     * @return $this
     * @throws LocalizedException
     */
    protected function insertNewSwatchOption($connection, $resource, $optionId, $swatchData, $attributeId)
    {
        if (!isset($swatchData['type']) && !isset($swatchData['value'])) {
            $table = $resource->getTable('eav_attribute_option_swatch');
            $data = [
                'option_id' => $optionId,
                'store_id' => 0,
                'type' => Swatch::SWATCH_TYPE_TEXTUAL,
                'value' => $swatchData,
            ];
            $connection->insert($table, $data);
            $this->cachedSwatchOptions[$attributeId][$optionId] = $data;
        } else {
            if ($swatchData['type'] == Swatch::SWATCH_TYPE_VISUAL_IMAGE) {
                $swatchData['value'] = $this->uploadVisualSwatchFile($swatchData['value']);
            }
            if ($swatchData['value']) {
                $table = $resource->getTable('eav_attribute_option_swatch');
                $data = [
                    'option_id' => $optionId,
                    'store_id' => 0,
                    'type' => $swatchData['type'],
                    'value' => $swatchData['value'],
                ];
                $connection->insert($table, $data);
                $this->cachedSwatchOptions[$attributeId][$optionId] = $data;
            }
        }

        return $this;
    }

    /**
     * @param $urlKey
     * @param $sku
     * @param $storeId
     *
     * @return string
     */
    protected function isDuplicateUrlKey($urlKey, $sku, $storeId)
    {
        $result = false;
        $urlKeyHtml = $urlKey . $this->getProductUrlSuffix();
        $resource = $this->getResource();
        $select = $this->_connection->select()->from(
            ['url_rewrite' => $resource->getTable('url_rewrite')],
            ['request_path', 'store_id']
        )->joinLeft(
            ['cpe' => $resource->getTable('catalog_product_entity')],
            'cpe.entity_id = url_rewrite.entity_id'
        )->where("request_path='$urlKey' OR request_path='$urlKeyHtml'")
            ->where('store_id IN (?)', $storeId)
            ->where('cpe.sku not in (?)', $sku);
        $isDuplicate = $this->_connection->fetchAssoc(
            $select
        );
        if (!empty($isDuplicate)) {
            $result = true;
        }
        return $result;
    }

    /**
     * @param array $rowData
     * @return string
     * @throws Exception
     */
    protected function getUrlKey($rowData)
    {
        $url = $this->productUrl->formatUrlKey(parent::getUrlKey($rowData));
        $this->urlKeyManager->addUrlKeys($rowData[self::COL_SKU], $url);
        return $url;
    }

    /**
     * @param array $rowData
     * @return string
     * @throws Exception
     */
    protected function getProductUrlKey($rowData)
    {
        if (isset($this->_parameters['enable_product_url_pattern']) &&
            $this->_parameters['enable_product_url_pattern'] === '1' &&
            isset($this->_parameters['product_url_pattern']) &&
            !empty($this->_parameters['product_url_pattern']) &&
            $this->validateProductUrlPattern($this->_parameters['product_url_pattern'])
        ) {
            return $this->generateUrlKeyByPattern($rowData);
        } else {
            return $this->getUrlKey($rowData);
        }
    }

    /**
     * @return array
     */
    protected function prepareUrlGeneratePattern()
    {
        if ($this->urlPatternData['cache'] == 0) {
            $pattern = $this->_parameters['product_url_pattern'];
            preg_match_all("/(?<=[[])[^]]+/", $pattern, $out, PREG_SET_ORDER);
            $functionsWithParameters = [];
            $fields = [];
            foreach ($out as $key => $value) {
                $pos = strripos($value[0], '(');
                if ($pos === false) {
                    $fields[] = $value[0];
                } else {
                    preg_match_all("/\((.+?|())\)/", $value[0], $functionParameters, PREG_SET_ORDER);
                    if (isset($functionParameters[0][1])) {
                        $str = strpos($value[0], "(");
                        $functionName = substr($value[0], 0, $str);
                        if (function_exists($functionName)) {
                            if (in_array($functionName, $this->urlPatternData['allowed_functions'])) {
                                if (!empty($functionParameters[0][1])) {
                                    $functionsWithParameters[$functionName] = $functionParameters[0][1];
                                } else {
                                    $functionsWithParameters[$functionName] = '';
                                }
                            } else {
                                $this->addLogWriteln(
                                    __(
                                        'Product Url Pattern can contain php functions: "%1" ',
                                        implode(", ", $this->urlPatternData['allowed_functions'])
                                    ),
                                    $this->getOutput(),
                                    'error'
                                );
                            }
                        }
                    }
                }
            }
            $this->urlPatternData = [
                'allowed_functions' => $this->urlPatternData['allowed_functions'],
                'fields' => $fields,
                'functions_with_parameters' => $functionsWithParameters,
                'cache' => 1
            ];

            return $this->urlPatternData;
        }
    }

    /**
     * @param $name
     * @param $sku
     * @param $rowData
     * @return array
     */
    protected function replacementInPatternValue($name, $sku, $rowData)
    {
        $replacement = [];

        if (isset($this->urlPatternData['fields'])
            && !empty($this->urlPatternData['fields'])) {
            foreach ($this->urlPatternData['fields'] as $key => $patternField) {
                if ($patternField == "product_name") {
                    array_push($replacement, $name);
                    continue;
                }
                if ($patternField == "product_sku") {
                    array_push($replacement, $sku);
                    continue;
                }
                preg_match("/". preg_quote('product_') . "(.*)/", $patternField, $field);
                if (isset($field[1])) {
                    if (isset($rowData[$field[1]])) {
                        array_push($replacement, $rowData[$field[1]]);
                    } else {
                        array_push($replacement, '');
                    }
                }
            }
        }

        if (isset($this->urlPatternData['functions_with_parameters'])
            && !empty($this->urlPatternData['functions_with_parameters'])) {
            foreach ($this->urlPatternData['functions_with_parameters'] as $functionName => $parameters) {
                if ($parameters) {
                    $parameters = explode(",", $parameters);
                    array_push($replacement, $functionName((int)$parameters[0], (int)$parameters[1]));
                } else {
                    array_push($replacement, $functionName());
                }
            }
        }

        return $replacement;
    }

    /**
     * @return array
     */
    protected function getProductUrlPatternVariables()
    {
        $this->prepareUrlGeneratePattern();
        $result = [];
        foreach ($this->urlPatternData['fields'] as $key => $patternField) {
            $result[] = "[$patternField]";
        }
        foreach ($this->urlPatternData['functions_with_parameters'] as $function => $parameters) {
            $result[] = "[$function($parameters)]";
        }

        return $result;
    }

    /**
     * @param string $productUrlPattern
     * @return bool
     */
    protected function validateProductUrlPattern($productUrlPattern)
    {
        foreach ($this->getProductUrlPatternVariables() as $variable) {
            if (strpos($productUrlPattern, $variable) !== false) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param array $rowData
     * @param $storeIds
     * @return string
     * @throws Exception
     */
    protected function generateUrlKeyByPattern($rowData, $storeIds = null)
    {
        $productEntityLinkField = $this->getProductEntityLinkField();
        $sku = $this->getCorrectSkuAsPerLength($rowData);
        $name = $rowData[self::COL_NAME] ?? '';
        if ($this->isSkuExist($sku)) {
            $exiting = $this->getExistingSku($sku);
            if (!$name) {
                $attr = $this->retrieveAttributeByCode(self::COL_NAME);
                $select = $this->getConnection()->select()
                    ->from($attr->getBackendTable(), ['value'])
                    ->where($productEntityLinkField . ' = (?)', $exiting['entity_id'])
                    ->where('attribute_id = (?)', $attr->getAttributeId());
                $name = $this->getConnection()->fetchOne($select);
            }
        }
        if ($storeIds === null) {
            $storeIds = $this->getStoreIds();
        }
        $replacement = $this->replacementInPatternValue($name, $sku, $rowData);
        $urlKey = str_replace(
            $this->getProductUrlPatternVariables(),
            $replacement,
            $this->_parameters['product_url_pattern']
        );
        $urlKey = $this->productUrl->formatUrlKey($urlKey);
        $isDuplicate = $this->isDuplicateUrlKey($urlKey, $sku, $storeIds);
        $storeId = $this->getRowStoreId($rowData);
        if ($isDuplicate && $this->urlKeyManager->isUrlKeyExist($sku, $urlKey, $storeId)) {
            $urlKey = $this->productUrl->formatUrlKey(
                $name . '-' . $sku
            );
        }
        $rowData[self::URL_KEY] = $urlKey;
        $this->urlKeyManager->addUrlKeys($sku, $urlKey, $storeId);
        return $urlKey;
    }

    /**
     * Divide additionalImages for old Magento version
     * @param $rowData
     *
     * @return mixed
     */
    protected function checkAdditionalImages($rowData)
    {
        return $this->importImageProcessor->checkAdditionalImages($rowData);
    }

    /**
     * @param array $rowData
     * @return array
     */
    public function stripSlashes(array $rowData)
    {
        foreach ($rowData as $key => $val) {
            if ($key === '') {
                continue;
            }
            if (!empty($val)) {
                $rowData[$key] = stripslashes((string) $val);
            }
        }

        return $rowData;
    }

    /**
     * @param array $rowData
     *
     * @return array
     */
    public function prepareRowForDb(array $rowData)
    {
        $rowData = $this->customFieldsMapping($rowData);

        $this->stripSlashes($rowData);

        static $lastSku = null;

        if (Import::BEHAVIOR_DELETE == $this->getBehavior()) {
            return $rowData;
        }

        $lastSku = $this->getCorrectSkuAsPerLength($rowData);

        if (version_compare($this->productMetadata->getVersion(), '2.2.0', '>=')) {
            $checkSku = strtolower($lastSku);
        } else {
            $checkSku = $lastSku;
        }
        if (isset($this->_oldSku[$checkSku]) && $this->_oldSku[$checkSku]) {
            $newSku = $this->skuProcessor->getNewSku($lastSku);
            if (isset($rowData[self::COL_ATTR_SET]) && !$rowData[self::COL_ATTR_SET]) {
                $rowData[self::COL_ATTR_SET] = $newSku['attr_set_code'];
            }
            if (isset($rowData[self::COL_TYPE]) && !$rowData[self::COL_TYPE]) {
                $rowData[self::COL_TYPE] = $newSku['type_id'];
            }
        }

        return $rowData;
    }

    /**
     * @param $rowData
     *
     * @return mixed
     */
    public function applyCategoryLevelSeparator($rowData)
    {
        $defaultCategoryName = '';
        $importCategoryName = '';
        if (isset($this->_parameters['root_category_id']) && $this->_parameters['root_category_id'] > 0) {
            $importCategoryId = (int)$this->_parameters['root_category_id'];
            /** @var \Magento\Catalog\Model\Category $importCategory */
            $importCategory = $this->categoryProcessor->getCategoryById($importCategoryId);
            if ($importCategory) {
                if ($importCategory->getParentCategory()->getId() != 1) {
                    $importCategoryName = $defaultCategoryName = $importCategory->getParentCategory()->getName();
                }
                if ((int)$importCategory->getId() === $importCategoryId && $importCategoryName !== '') {
                    $importCategoryName .= '/' . $importCategory->getName();
                } else {
                    $defaultCategoryName = $importCategoryName = $importCategory->getName();
                }
            }
        } elseif (isset($rowData['_root_category'])) {
            $importCategoryName = $defaultCategoryName = $rowData['_root_category'];
        }

        foreach ([self::COL_CATEGORY, self::COL_CATEGORY. '_position'] as $field) {
            if (!empty($rowData[$field])) {
                $rowData[$field] = $this->prepareCategoryPath($rowData[$field]);
            }
        }

        $categories = [];
        if ($defaultCategoryName && $importCategoryName && isset($rowData[self::COL_CATEGORY])) {
            foreach (explode($this->_parameters['categories_separator'], $rowData[self::COL_CATEGORY]) as $category) {
                if (strpos(trim($category), $defaultCategoryName) !== false) {
                    $categories[] = trim($category);
                } else {
                    $categories[] = rtrim($importCategoryName . '/' . trim($category), '/');
                }
            }
            $rowData[self::COL_CATEGORY] = implode($this->_parameters['categories_separator'], $categories);
        } elseif ($importCategoryName) {
            $rowData[self::COL_CATEGORY] = $importCategoryName;
        }
        return $rowData;
    }

    /**
     * @param string $path
     *
     * @return string
     */
    protected function prepareCategoryPath($path)
    {
        $parts = [];
        $separator = $this->_parameters['category_levels_separator'];
        foreach (explode($separator, $path) as $part) {
            if ($part) {
                $parts[] = str_replace('/', '&#47;', $part);
            }
        }
        return implode('/', $parts);
    }

    /**
     * @param $array
     *
     * @return array
     */
    protected function deleteEmpty($array)
    {
        if (isset($array[self::COL_SKU])) {
            unset($array[self::COL_SKU]);
        }
        $newElement = [];
        foreach ($array as $key => $element) {
            if (strlen($element)) {
                $newElement[$key] = $element;
            }
        }

        return $newElement;
    }

    protected function getCategories($rowData)
    {
        $ids = [];
        if (isset($rowData[self::COL_STORE])) {
            $this->categoryProcessor->setStoreId($this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE]));
        }
        $this->categoryProcessor->setGeneratUrl($this->_parameters['generate_url']);
        $this->categoryProcessor->setResource($this->getResource());

        try {
            $ids = $this->categoryProcessor->getRowCategories(
                $rowData,
                $this->_parameters['categories_separator']
            );
        } catch (\Exception $e) {
            $this->errorAggregator->addError(
                AbstractEntity::ERROR_CODE_CATEGORY_NOT_VALID,
                ProcessingError::ERROR_LEVEL_NOT_CRITICAL,
                $rowData['rowNum'],
                self::COL_CATEGORY . '_position',
                __('Please correct the value for category position.')
            );
            $this->errorAggregator->addRowToSkip($rowData['rowNum']);
        }

        foreach ($this->categoryProcessor->getFailedCategories() as $error) {
            $this->errorAggregator->addError(
                AbstractEntity::ERROR_CODE_CATEGORY_NOT_VALID,
                ProcessingError::ERROR_LEVEL_NOT_CRITICAL,
                $rowData['rowNum'],
                self::COL_CATEGORY,
                __('Category "%1" has not been created.', $error['category'])
                . ' ' . $error['exception']->getMessage()
            );
        }

        $this->categoryProcessor->clearFailedCategories();

        return $ids;
    }

    /**
     * @param $rowData
     * @return mixed
     * @throws NoSuchEntityException
     */
    protected function applyPriceRules($rowData)
    {
        if (!empty($this->_parameters['price_rules'])) {
            $priceRules = $this->_parameters['price_rules'];

            foreach ($priceRules as $priceRule) {
                $applyRule = true;

                if (isset($priceRule['price_rules_conditions_hidden']['rule']['conditions'])) {
                    $setId = null;
                    if (isset($rowData['_attribute_set']) &&
                        isset($this->_attrSetNameToId[$rowData['_attribute_set']])
                    ) {
                        $setId = $this->_attrSetNameToId[$rowData['_attribute_set']];
                    }

                    $rowScope = empty($rowData[self::COL_STORE]) ? self::SCOPE_DEFAULT : self::SCOPE_STORE;
                    $storeId = (self::SCOPE_STORE == $rowScope)
                        ? $this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE])
                        : 0;

                    $data = [
                        'conditions' => $priceRule['price_rules_conditions_hidden']['rule'],
                        'row' => $rowData,
                        'attribute_set_id' => $rowData['attribute_set_id'] ?? $setId,
                        'store_id' => $rowData['store_id'] ?? $storeId,
                        'categories' => isset($this->categoriesCache[$this->getCorrectSkuAsPerLength($rowData)]) ?
                            array_keys($this->categoriesCache[$this->getCorrectSkuAsPerLength($rowData)]) : [],
                    ];

                    if (empty($data['categories'])) {
                        $checkSku = $this->getCorrectSkuAsPerLength($rowData);
                        if (isset($this->_oldSku[strtolower($checkSku)])) {
                            /** @var \Magento\Catalog\Model\Product $product */
                            $product = $this->productRepository->get($checkSku);
                            $data['categories'] = $product->getCategoryIds();
                        }
                    }
                    $applyRule = $this->priceRuleConditionFactory->create()->validatePriceRuleConditions($data);
                }

                if ($applyRule && isset($priceRule['apply'])) {
                    if ($priceRule['apply'] == 'fixed') {
                        $rowData['price'] += $priceRule['value'];
                    } else {
                        $rowData['price'] *= 1 + $priceRule['value'] / 100;
                    }
                }
            }
        }

        if (isset($this->_parameters['round_up_prices'], $rowData['price'])
            && $this->_parameters['round_up_prices'] > 0) {
            $rowData['price'] = $this->roundPrice($rowData['price']);
        }

        if (isset($this->_parameters['round_up_special_price'], $rowData['special_price'])
            && $this->_parameters['round_up_special_price'] > 0) {
            $rowData['special_price'] = $this->roundPrice($rowData['special_price']);
        }

        return $rowData;
    }

    /**
     * Round price to *.49 or to *.99
     *
     * @param $num
     *
     * @return float
     */
    protected function roundPrice($num): float
    {
        $num = (float)$num;
        $fln = $num - floor($num);
        if ($fln > 0 && $fln < 0.5) {
            $fln = 0.49;
        } else {
            $fln = 0.99;
        }

        return floor($num) + $fln;
    }

    /**
     * @param array $data
     * @param string $rowSku
     *
     * @return array
     */
    protected function getTierPrices($data, $rowSku)
    {
        $tierPrices = [];

        if (!empty($data['tier_prices'])) {
            $tiers = explode("|", $data['tier_prices']);
            $groups = $this->groupFactory->create()->getCollection()->toOptionArray();
            $newGroups = [];
            foreach ($groups as $group) {
                $newGroups[$group['label']] = $group['value'];
            }
            $websites = $this->websiteFactory->create()->getCollection()->getItems();
            $newWebsites = [0 => self::VALUE_ALL];
            foreach ($websites as $website) {
                $newWebsites[$website->getCode()] = $website->getWebsiteId();
            }
            foreach ($tiers as $field) {
                $elements = array_map('trim', explode(self::DEFAULT_GLOBAL_MULTIPLE_VALUE_SEPARATOR, $field));
                $isAllGroup = 0;
                if ($elements[0] == __('ALL GROUPS')) {
                    $isAllGroup = 1;
                }

                if (version_compare($this->productMetadata->getVersion(), '2.2.0', '>=')) {
                    $tierPrices[$rowSku][] = [
                        'all_groups' => $isAllGroup,
                        'customer_group_id' => (isset($elements[0]) && isset($newGroups[$elements[0]])) ?
                            $newGroups[$elements[0]] : 0,
                        'qty' => (isset($elements[1])) ? $elements[1] : 0,
                        'value' => (isset($elements[2])) ? $elements[2] : 0,
                        'percentage_value' => (isset($elements[3])) ?
                            (!empty($elements[3]) ? $elements[3] : null) : null,
                        'website_id' => $this->storeResolver->getWebsiteCodeToId(
                            $elements[4]
                        )
                    ];
                } else {
                    $tierPrices[$rowSku][] = [
                        'all_groups' => $isAllGroup,
                        'customer_group_id' => (isset($elements[0]) && isset($newGroups[$elements[0]])) ?
                            $newGroups[$elements[0]] : 0,
                        'qty' => (isset($elements[1])) ? $elements[1] : 0,
                        'value' => (isset($elements[2])) ? $elements[2] : 0,
                        'website_id' => (isset($elements[3]) && isset($newWebsites[$elements[3]])) ?
                            $newWebsites[$elements[3]] : 0,
                    ];
                }
            }
        }
        return $tierPrices;
    }

    /**
     * Save product tier prices.
     *
     * @param array $tierPriceData
     * @return $this
     */
    protected function _saveProductTierPrices(array $tierPriceData)
    {
        static $tableName = null;

        if (!$tableName) {
            $tableName = $this->_resourceFactory->create()->getTable('catalog_product_entity_tier_price');
        }
        if ($tierPriceData) {
            $tierPriceIn = [];
            $delProductId = [];

            foreach ($tierPriceData as $delSku => $tierPriceRows) {
                $productId = $this->skuProcessor->getNewSku($delSku)[$this->getProductEntityLinkField()];
                $delProductId[] = $productId;

                foreach ($tierPriceRows as $row) {
                    $row[$this->getProductEntityLinkField()] = $productId;
                    $tierPriceIn[] = $row;
                }
            }
            if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
                $this->_connection->delete(
                    $tableName,
                    $this->_connection->quoteInto("{$this->getProductEntityLinkField()} IN (?)", $delProductId)
                );
            }
            if ($tierPriceIn) {
                $this->_connection->insertOnDuplicate($tableName, $tierPriceIn, ['value', 'percentage_value']);
            }
        }
        return $this;
    }

    /**
     * @param $data
     * @param array $existingImages
     * @return $this
     */
    protected function saveConfigurationVariations($data, $existingImages = [])
    {
        if (!empty($data)) {
            $configurableProductsData = [];
            /**
             * @var string $skuConf
             * @var array $elements
             */
            foreach ($data as $skuConf => $elements) {
                $skuConf = (string) $skuConf;
                if (count($elements) < 1) {
                    continue;
                }
                $firstElement = current($elements) ?? [];
                $configurableProductVisibility =
                    $firstElement[Product\ConfigurationVariations::FIELD_COPY_VALUE][ProductInterface::VISIBILITY] ??
                    Visibility::VISIBILITY_BOTH;
                $configurableProductStatus =
                    $firstElement[Product\ConfigurationVariations::FIELD_COPY_VALUE][ProductInterface::STATUS] ??
                    Status::STATUS_ENABLED;
                $fieldCopyValue = $firstElement[Product\ConfigurationVariations::FIELD_COPY_VALUE] ?? [];

                foreach ($elements as &$elementLink) {
                    unset($elementLink[Product\ConfigurationVariations::FIELD_COPY_VALUE]);
                    unset($elementLink[Product\ConfigurationVariations::FIELD_CONF_IMPORT]);
                }
                if (version_compare($this->productMetadata->getVersion(), '2.2.0', '>=')) {
                    $checkSku = mb_strtolower($skuConf);
                } else {
                    $checkSku = $skuConf;
                }
                $additionalRows = [];
                $changeAttributes = [];
                $mediaGallery = [];
                $updateData = [];
                $storeIds = $this->getStoreIds();
                if (!empty($fieldCopyValue)) {
                    $updateData['eav_attributes'] = $fieldCopyValue;
                    unset(
                        $updateData['eav_attributes'][SystemOptions::RELATED_PRODUCT_ATTRIBUTE],
                        $updateData['eav_attributes'][SystemOptions::UP_SELLS_PRODUCT_ATTRIBUTE],
                        $updateData['eav_attributes'][SystemOptions::CROSS_SELLS_PRODUCT_ATTRIBUTE],
                        $updateData['eav_attributes']['category_ids'],
                        $updateData['eav_attributes']['_media_image'],
                        $updateData['eav_attributes']['_media_image_label']
                    );
                }
                try {
                    $this->addLogWriteln(__('Configure variations for SKU:%1', $skuConf), $this->output, 'info');
                    if ($this->isNeedToCreateConfigurableProduct($checkSku)) {
                        try {
                            $updateData[self::COL_SKU] = $skuConf;
                            if (empty($updateData['eav_attributes'][self::COL_NAME])) {
                                $updateData['eav_attributes'][self::COL_NAME] = $skuConf;
                            }
                            $updateData['eav_attributes'][ProductInterface::STATUS] =
                                $configurableProductStatus ?? Status::STATUS_ENABLED;
                            $updateData['eav_attributes'][ProductInterface::VISIBILITY] =
                                $configurableProductVisibility ?? Visibility::VISIBILITY_BOTH;

                            $updateData['attribute_set_id']
                                = $firstElement[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['attribute_set_id'];
                            $updateData['type_id'] = TypeConfigurable::TYPE_CODE;
                            $updateData['website_ids']
                                = $firstElement[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['website_ids'];
                            $updateData['category_ids']
                                = $firstElement[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['category_ids'] ??
                                '';
                            if (empty($updateData['eav_attributes'][self::URL_KEY])) {
                                $storeIds = $this->getStoreIds();
                                $updateData[self::COL_NAME] = $updateData['eav_attributes'][self::COL_NAME];
                                $updateData = $this->generateUrlKey($updateData, $storeIds);
                                $updateData['eav_attributes'][self::URL_KEY] = $updateData[self::URL_KEY];
                                unset($updateData[self::URL_KEY]);
                                unset($updateData[self::COL_NAME]);
                            }
                            $this->configVariations->saveNewProduct($updateData);
                            if (!empty($updateData['website_ids'])) {
                                foreach ($updateData['website_ids'] as $websiteId) {
                                    $this->websitesCache[$skuConf][$websiteId] = true;
                                }
                            }
                            $configurableProductsData[$skuConf] = $updateData;
                            if (!empty($updateData['eav_attributes']['visibility'])) {
                                $configurableProductsData[$skuConf]['visibility'] =
                                    $updateData['eav_attributes']['visibility'];
                            }
                            if (!empty($updateData['eav_attributes']['status'])) {
                                $configurableProductsData[$skuConf]['status'] =
                                    $updateData['eav_attributes']['status'];
                            }
                            if (!empty($updateData['eav_attributes']['url_key'])) {
                                $configurableProductsData[$skuConf]['url_key'] =
                                        $updateData['eav_attributes']['url_key'];
                            }
                            $entityLinkField = $this->getProductEntityLinkField();
                            $this->skuProcessor->addNewSku($skuConf, $updateData);
                            $this->_oldSku[strtolower($skuConf)] = [
                                'type_id' => TypeConfigurable::TYPE_CODE,
                                'attr_set_id'
                                => $updateData['attribute_set_id'],
                                $entityLinkField => $updateData[$entityLinkField],
                                $this->getProductIdentifierField() => $updateData[$this->getProductIdentifierField()],
                                'supported_type' => true,
                            ];

                            $parentProductId = $updateData[$this->getProductEntityLinkField()];
                            foreach ($fieldCopyValue as $key => $attr) {
                                switch ($key) {
                                    case SystemOptions::RELATED_PRODUCT_ATTRIBUTE:
                                        $this->productLinkData[$parentProductId]['type'][] =
                                            ProductLink::LINK_TYPE_RELATED;
                                        break;
                                    case SystemOptions::UP_SELLS_PRODUCT_ATTRIBUTE:
                                        $this->productLinkData[$parentProductId]['type'][] =
                                            ProductLink::LINK_TYPE_UPSELL;
                                        break;
                                    case SystemOptions::CROSS_SELLS_PRODUCT_ATTRIBUTE:
                                        $this->productLinkData[$parentProductId]['type'][] =
                                            ProductLink::LINK_TYPE_CROSSSELL;
                                        break;
                                }
                            }
                        } catch (LocalizedException $e) {
                            $this->addLogWriteln($e->getMessage(), $this->output, 'error');
                        }
                    } else {
                        if ($this->isSkuExist($checkSku)) {
                            $productParent = $this->getExistingSku($checkSku);
                            $firstElement = current($data[$skuConf]);
                            $configurableProductStatus =
                                $firstElement[Product\ConfigurationVariations::FIELD_COPY_VALUE]
                                [ProductInterface::STATUS] ?? [];
                            $configurableProductVisibility =
                                $firstElement[Product\ConfigurationVariations::FIELD_COPY_VALUE]
                                [ProductInterface::VISIBILITY] ?? [];
                            if (!empty($configurableProductVisibility)) {
                                $updateData['eav_attributes'][ProductInterface::VISIBILITY] =
                                    $configurableProductVisibility;
                            }
                            if (!empty($configurableProductStatus)) {
                                $updateData['eav_attributes'][ProductInterface::STATUS] =
                                    $configurableProductStatus;
                            }

                            $parentProductId = $productParent[$this->getProductEntityLinkField()];
                            foreach ($fieldCopyValue as $key => $attr) {
                                switch ($key) {
                                    case SystemOptions::RELATED_PRODUCT_ATTRIBUTE:
                                        $this->productLinkData[$parentProductId]['type'][] =
                                            ProductLink::LINK_TYPE_RELATED;
                                        break;
                                    case SystemOptions::UP_SELLS_PRODUCT_ATTRIBUTE:
                                        $this->productLinkData[$parentProductId]['type'][] =
                                            ProductLink::LINK_TYPE_UPSELL;
                                        break;
                                    case SystemOptions::CROSS_SELLS_PRODUCT_ATTRIBUTE:
                                        $this->productLinkData[$parentProductId]['type'][] =
                                            ProductLink::LINK_TYPE_CROSSSELL;
                                        break;
                                }
                            }

                            $updateData['attribute_set_id']
                                = $firstElement[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['attribute_set_id'];
                            $updateData['type_id'] = TypeConfigurable::TYPE_CODE;
                            $updateData['website_ids']
                                = $firstElement[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['website_ids'];
                            $updateData['category_ids'] =
                                $firstElement[Product\ConfigurationVariations::FIELD_CONF_IMPORT]['category_ids'] ?? '';
                            $updateData[$this->getProductEntityLinkField()]
                                = $productParent[$this->getProductEntityLinkField()];
                            $updateData[$this->getProductIdentifierField()]
                                = $productParent[$this->getProductIdentifierField()];
                            $this->configVariations->updateProduct($updateData);
                            if ($productParent['type_id'] != TypeConfigurable::TYPE_CODE) {
                                $this->configVariations->updateTypeProductToConfigurable(
                                    (int)$updateData[$this->getProductIdentifierField()]
                                );
                            }
                        } else {
                            $this->addLogWriteln(
                                __(
                                    'Configurable Product for sku "%1" not created before. Turn on feature to create ' .
                                    'configurable product on the fly',
                                    $skuConf
                                ),
                                $this->getOutput(),
                                'error'
                            );

                            continue;
                        }
                    }

                    if (isset($this->_parameters['remove_images'])
                        && array_key_exists($skuConf, $existingImages)
                        && $this->_parameters['remove_images'] == 1
                    ) {
                        $this->removeExistingImages($existingImages[$skuConf]);
                        unset($existingImages[$skuConf]);
                    }

                    foreach ($this->_imagesArrayKeys as $fieldImage) {
                        if ($fieldImage === '_media_image') {
                            $copyValues = $firstElement[Product\ConfigurationVariations::FIELD_COPY_VALUE];
                            if (isset($copyValues[$fieldImage])) {
                                $mediaImage = $copyValues[$fieldImage];
                                $mediaImageLabel = $copyValues['_media_image_label'] ?? '';
                                $mediaGallery[Store::DEFAULT_STORE_ID][$skuConf][] = [
                                    'attribute_id' => $this->getMediaGalleryAttributeId(),
                                    'label' => $mediaImageLabel,
                                    'position' => 1,
                                    'disabled' => '0',
                                    'value' => $mediaImage,
                                ];
                                continue;
                            }
                        }
                        if ($fieldImage === ProductVideo::VIDEO_URL_COLUMN) {
                            continue;
                        }
                        if (empty($updateData['eav_attributes'][$fieldImage])) {
                            continue;
                        }
                        $attributeChange = $this->retrieveAttributeByCode($fieldImage);
                        $attrId = $attributeChange->getId();
                        $attrTable = $attributeChange->getBackend()->getTable();
                        $attrValue = $updateData['eav_attributes'][$fieldImage];
                        if (!isset($changeAttributes[$attrTable][$checkSku][$attrId][0]) && !empty($attrValue)) {
                            $changeAttributes[$attrTable][$skuConf][$attrId][0] = $attrValue;
                            if (version_compare($this->productMetadata->getVersion(), '2.2.4', '>=') ||
                                strpos($this->getProductMetadata()->getVersion(), '1.0.0') !== false) {
                                $mediaGallery[Store::DEFAULT_STORE_ID][$skuConf][] = [
                                    'attribute_id' => $this->getMediaGalleryAttributeId(),
                                    'label' => '',
                                    'position' => 1,
                                    'disabled' => '0',
                                    'value' => $attrValue,
                                ];
                            } else {
                                $mediaGallery[$skuConf][] = [
                                    'attribute_id' => $this->getMediaGalleryAttributeId(),
                                    'label' => '',
                                    'position' => 1,
                                    'disabled' => '0',
                                    'value' => $attrValue,
                                ];
                            }
                        }
                    }

                    $vars = [];
                    $attributes = [];
                    $visAttribute = $this->retrieveAttributeByCode(ProductInterface::VISIBILITY);
                    $statusAttribute = $this->retrieveAttributeByCode(ProductInterface::STATUS);
                    $visAttrTable = $visAttribute->getBackend()->getTable();
                    $visAttrId = $visAttribute->getId();
                    $statusAttrTable = $statusAttribute->getBackend()->getTable();
                    $statusAttrId = $statusAttribute->getId();
                    foreach ($elements as $element) {
                        $position = 0;
                        foreach ($element as $attributeCode => $field) {
                            if ($attributeCode != ProductInterface::SKU && !empty($field)) {
                                if (!in_array($attributeCode, $attributes)) {
                                    $attributes[] = $attributeCode;
                                }
                                $vars['fields'][] = [
                                    'code' => $attributeCode,
                                    'value' => $field,
                                ];
                            } else {
                                if ($attributeCode == ProductInterface::SKU) {
                                    foreach ($storeIds as $storeId) {
                                        if (empty($changeAttributes[$visAttrTable][$field][$visAttrId][$storeId])) {
                                            $changeAttributes[$visAttrTable][$field][$visAttrId][$storeId] =
                                                $element[ProductInterface::VISIBILITY] ?? 1;
                                        }
                                        if (empty(
                                            $changeAttributes[$statusAttrTable][$field][$statusAttrId][$storeId]
                                        )) {
                                            $changeAttributes[$statusAttrTable][$field][$statusAttrId][$storeId] =
                                                $element[ProductInterface::STATUS] ?? 1;
                                        }
                                    }
                                }
                                $vars[$attributeCode] = $field;
                            }
                        }
                        $vars['position'] = $position;
                        $position++;
                        $additionalRows[] = $vars;
                    }
                    $attributeValues = [];
                    $ids = [];
                    $configurableAttributesData = [];
                    $position = 0;
                    /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute|null $attributeConf */
                    $attributeConf = null;
                    foreach ($attributes as $attribute) {
                        foreach ($additionalRows as $list) {
                            $attributeConf = $this->retrieveAttributeByCode($attribute);
                            $value = [];
                            if (isset($list['fields'])) {
                                foreach ($list['fields'] as $item) {
                                    if ($item['code'] == $attribute) {
                                        $value = $item['value'];
                                        $existingSku = $this->getExistingSku(isset($list['sku']) ? $list['sku']:'');
                                        $simpleProductId = is_array($existingSku)
                                            ? $existingSku[$this->getProductIdentifierField()] : false;
                                        if (!in_array($simpleProductId, $ids)) {
                                            $ids[] = $simpleProductId;
                                        }
                                    }
                                }
                            }
                            if (!empty($attributeConf)) {
                                $attributeValues[$attribute][] = [
                                    'label' => $attribute,
                                    'attribute_id' => $attributeConf->getId(),
                                    'value_index' => $value,
                                ];
                            }
                        }
                        if (!empty($attributeConf)) {
                            $configurableVariation = $this->_parameters['configurable_variations'] ?? [];
                            if (!empty($configurableVariation) &&
                                in_array(
                                    strtolower($attributeConf->getAttributeCode()),
                                    array_map('strtolower', $configurableVariation)
                                )
                            ) {
                                $configurableAttributesData[] =
                                    [
                                        'attribute_id' => $attributeConf->getId(),
                                        'code' => $attributeConf->getAttributeCode(),
                                        'label' => $attributeConf->getStoreLabel(),
                                        'position' => $position++,
                                        'values' => $attributeValues[$attribute],
                                    ];
                            }
                        }
                    }

                    if (!empty($mediaGallery)) {
                        $this->_saveMediaGallery($mediaGallery);
                    }
                    if (!empty($changeAttributes)) {
                        $this->_saveProductAttributes($changeAttributes);
                    }
                    if (!empty($updateData[$this->getProductEntityLinkField()])) {
                        $this->saveCollectData(
                            $updateData[$this->getProductEntityLinkField()],
                            $configurableAttributesData,
                            $ids
                        );
                        if (!empty($updateData['entity_id'])) {
                            $stockData = [];
                            $defaultScopeConfig = $this->stockConfiguration->getDefaultScopeId();
                            $stockData[$skuConf] = [
                                'is_in_stock' => 1,
                                'product_id' => $updateData['entity_id'],
                                'website_id' => $defaultScopeConfig,
                                'stock_id' => $this->stockRegistry->getStock($defaultScopeConfig)->getStockId(),
                            ];
                            try {
                                $this->stockItemImporter->import($stockData);
                            } catch (Exception $exception) {
                                $this->addLogWriteln($exception->getMessage(), $this->getOutput(), 'info');
                                $this->getLogger()->debug($exception);
                            }
                        }
                    }
                } catch (Exception $e) {
                    $this->getErrorAggregator()->addError(
                        $e->getCode(),
                        ProcessingError::ERROR_LEVEL_NOT_CRITICAL,
                        null,
                        null,
                        $e->getMessage()
                    );
                }
            }
            $this->_eventManager->dispatch(
                'catalog_product_import_bunch_save_after',
                ['adapter' => $this, 'bunch' => $configurableProductsData]
            );
        }

        return $this;
    }

    /**
     * @param $checkSku
     * @return bool
     */
    protected function isNeedToCreateConfigurableProduct($checkSku)
    {
        $jobId = $this->_parameters['job_id'] ?? '';
        $cachedRowSkus = $this->cache->load(ImportProductCache::ROW_SKUS_CACHE_ID . $jobId);
        $cachedRowSkus = $cachedRowSkus ? $this->getSerializer()->unserialize($cachedRowSkus) : [];

        if (!empty($this->_parameters['configurable_create']) &&
            !$this->isSkuExist($checkSku) ||
            (empty($this->_parameters['configurable_create']) &&
                !$this->isSkuExist($checkSku) &&
                in_array($checkSku, $cachedRowSkus))
        ) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Add related, cross-sell or up-sell products
     *
     * @param $configurableProductId
     * @param $typesLink
     * @throws LocalizedException
     */
    protected function addProductLinks($configurableProductId, $typesLink)
    {
        $resource = $this->_linkFactory->create();
        $mainTable = $this->productLink->getLinkCollection()->getMainTable();
        $nextLinkId = $this->_resourceHelper->getNextAutoincrement($mainTable);
        $allLinkedProducts = [];
        $positionRows = [];
        $select = $this->_connection->select()
            ->from($this->getResource()->getTable('catalog_product_relation'))
            ->where('parent_id=?', $configurableProductId);

        // For Magento EE. Change child_id to row_id.
        if ($this->getProductEntityLinkField() == 'row_id') {
            $select->joinLeft(
                ['cpe' => $this->getResource()->getTable('catalog_product_entity')],
                'catalog_product_relation.child_id = cpe.entity_id',
                ['child_id' => 'row_id']
            );
        }

        $simpleProductId = $this->_connection->fetchRow($select)['child_id'];

        $select = $this->_connection->select()
            ->from($mainTable)
            ->reset(Select::COLUMNS)
            ->columns(['linked_product_id', 'link_type_id'])
            ->where('product_id=?', $simpleProductId);
        $linkedProducts = $this->_connection->fetchAll($select);
        $this->_connection->delete(
            $mainTable,
            "product_id=$configurableProductId"
        );
        $i = 0;
        foreach ($linkedProducts as $product) {
            if (in_array($product['link_type_id'], $typesLink['type'])) {
                $product['product_id'] = $configurableProductId;
                $product['link_id'] = $nextLinkId;
                $allLinkedProducts[] = $product;
                $positionRows[] = [
                    'link_id' => $nextLinkId,
                    'product_link_attribute_id' => $product['link_type_id'],
                    'value' => $i++,
                ];
                $nextLinkId++;
            }
        }
        if (isset($allLinkedProducts) && !empty($allLinkedProducts)) {
            $this->_connection->insertMultiple(
                $mainTable,
                $allLinkedProducts
            );
            $this->_connection->insertOnDuplicate(
                $resource->getAttributeTypeTable('int'),
                $positionRows,
                ['value']
            );
        }
    }

    /**
     * @param array $websiteData
     * @param null $productId
     * @param bool $config
     *
     * @return $this|MagentoProduct
     */
    protected function _saveProductWebsites(array $websiteData, $productId = null, $config = false)
    {
        static $productWebsiteTable = null;
        $removeWebsite = $this->_parameters['remove_product_website'] ?? 0;
        if ($removeWebsite || $productId) {
            if (!$productWebsiteTable) {
                $productWebsiteTable = $this->getResource()->getProductWebsiteTable();
            }
            if ($websiteData) {
                $newWebsiteData = [];
                $deletedProductIds = [];

                foreach ($websiteData as $productSku => $productWebsites) {
                    if ($this->onlyUpdate
                        && $removeWebsite > 0
                        && empty($productWebsites)
                    ) {
                        $this->addLogWriteln(
                            __('Product %1 Website Cannot be Cleared', $productSku),
                            $this->output,
                            'info'
                        );
                        continue;
                    }
                    if (!$config) {
                        $productId = $this->skuProcessor->getNewSku($productSku)['entity_id'];
                    }
                    $deletedProductIds[] = $productId;
                    if ($config) {
                        foreach ($productWebsites as $websiteId) {
                            $newWebsiteData[] = ['product_id' => $productId, 'website_id' => $websiteId];
                        }
                    } else {
                        foreach (array_keys($productWebsites) as $websiteId) {
                            $newWebsiteData[] = ['product_id' => $productId, 'website_id' => $websiteId];
                        }
                    }
                }
                $this->_connection->delete(
                    $productWebsiteTable,
                    $this->_connection->quoteInto('product_id IN (?)', $deletedProductIds)
                );

                if ($newWebsiteData) {
                    $this->_connection->insertOnDuplicate($productWebsiteTable, $newWebsiteData);
                }
            }
            return $this;
        }
        return parent::_saveProductWebsites($websiteData);
    }

    /**
     * @param int $productId
     * @param array $configurableAttributesData
     * @param array $ids
     * @throws Exception
     */
    public function saveCollectData($productId, $configurableAttributesData, $ids)
    {
        $connection = $this->_connection;
        $resource = $this->getResource();
        $table = $resource->getTable('catalog_product_super_attribute');
        $labelTable = $resource->getTable('catalog_product_super_attribute_label');
        $linkTable = $resource->getTable('catalog_product_super_link');
        $relationTable = $resource->getTable('catalog_product_relation');
        $select = $connection->select()->from(
            ['m' => $table],
            ['product_id', 'attribute_id', 'product_super_attribute_id']
        )->where(
            'm.product_id IN ( ? )',
            [$productId]
        );

        //check if import source has different product variation for this product then delete current variations
        $counts = count($connection->fetchAll($select));
        if ($counts) {
            $isChangeOfAttributeVariation = false;
            if ($counts != count($configurableAttributesData)) {
                $isChangeOfAttributeVariation = true;
            } else {
                $currentattrConfig = $connection->fetchAll($select);
                $currentAttributeIds = [];
                if (!empty($currentattrConfig)) {
                    foreach ($currentattrConfig as $key => $value) {
                        $currentAttributeIds[] = $value['attribute_id'];
                    }
                }

                $newAttrIds = [];
                foreach ($configurableAttributesData as $elem) {
                    $newAttrIds[] = $elem['attribute_id'];
                }
                sort($currentAttributeIds);
                sort($newAttrIds);
                if ($currentAttributeIds != $newAttrIds) {
                    $isChangeOfAttributeVariation = true;
                }
            }
            if ($isChangeOfAttributeVariation) {
                $whereConditions = [
                    $connection->quoteInto('product_id = ?', $productId),
                ];
                $deleteRows = $connection->delete($table, $whereConditions);
            }
        }

        //inser new product variation for configurable product
        $counts = count($connection->fetchAll($select));
        if (!$counts) {
            foreach ($configurableAttributesData as $elem) {
                $data = [
                    'product_id' => $productId,
                    'attribute_id' => $elem['attribute_id'],
                    'position' => $elem['position'],
                ];
                $connection->insertOnDuplicate($table, $data);
            }

            foreach ($connection->fetchAll($select) as $row) {
                $attrId = $row['attribute_id'];
                $superId = $row['product_super_attribute_id'];
                foreach ($configurableAttributesData as $elem) {
                    if ($elem['attribute_id'] == $attrId) {
                        $data = ['product_super_attribute_id' => $superId, 'value' => $elem['label']];
                        $connection->insertOnDuplicate($labelTable, $data);
                    }
                }
            }
        }
        $first = 0;
        foreach ($ids as $id) {
            $data = ['product_id' => $id, 'parent_id' => $productId];
            $connection->insertOnDuplicate($linkTable, $data);
            if ($this->manager->isEnabled('Firebear_ConfigurableProducts') && !$first) {
                $connection->insertOnDuplicate(
                    $resource->getTable('icp_catalog_product_default_super_link'),
                    $data
                );
                $first = 1;
            }
            $relData = ['child_id' => $id, 'parent_id' => $productId];
            $connection->insertOnDuplicate($relationTable, $relData);
        }
    }

    /**
     * @param string $sku
     * @return bool
     */
    public function isExist($sku)
    {
        if ($this->onlyUpdate) {
            $collectionUpdate = $this->collectionFactory->create()->addFieldToFilter(
                self::COL_SKU,
                $sku
            );
            if (!$collectionUpdate->getSize()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Load categories map
     *
     * @param string $fieldName
     * @return array
     * @throws LocalizedException
     */
    public function getCategoriesMap($fieldName)
    {
        $bunchRows = [];
        $categories = [];
        $source = $this->_getSource();
        $source->rewind();
        $i = 1;
        while ($source->valid() || $bunchRows) {
            if ($source->valid()) {
                $rowData = $source->current();
                if (isset($rowData[$fieldName])) {
                    $categories[] = $rowData[$fieldName];
                }
                $i++;
                $source->next();
            }
        }

        return $categories;
    }

    /**
     * Validate data
     *
     * @param int $saveBunches
     * @return ProcessingErrorAggregatorInterface
     * @throws LocalizedException
     * @throws Exception
     */
    public function validateData($saveBunches = 1)
    {
        if ($this->_parameters['behavior'] == Import::FIREBEAR_ONLY_UPDATE) {
            $this->onlyUpdate = 1;
            $this->_parameters['behavior'] = Import::BEHAVIOR_APPEND;
        } elseif ($this->_parameters['behavior'] == Import::FIREBEAR_ONLY_ADD) {
            $this->onlyAdd = true;
            $this->_parameters['behavior'] = Import::BEHAVIOR_APPEND;
        }

        if (isset($this->_parameters['output'])) {
            $this->output = $this->_parameters['output'];
        }

        if (!$this->_dataValidated) {
            $this->getErrorAggregator()->clear();
            // do all permanent columns exist?
            $platformModel = null;
            $absentColumns =
                array_diff($this->_permanentAttributes, $this->getSource()->getColNames());
            $this->addErrors(self::ERROR_CODE_COLUMN_NOT_FOUND, $absentColumns);

            // check attribute columns names validity
            $columnNumber = 0;
            $emptyHeaderColumns = [];
            $invalidColumns = [];
            $invalidAttributes = [];
            foreach ($this->getSource()->getColNames() as $columnName) {
                $this->addLogWriteln(__('Checked column %1', $columnName), $this->output);
                $columnNumber++;
                if (!$this->isAttributeParticular($columnName)) {
                    /**
                     * Check syntax when attribute should be created on the fly
                     */
                    $createValuesAllowed = (bool)$this->scopeConfig->getValue(
                        Import::CREATE_ATTRIBUTES_CONF_PATH,
                        ScopeInterface::SCOPE_STORE
                    );
                    if ($createValuesAllowed && preg_match('/^(attribute\|).+/', $columnName)) {
                        $attrCodes = [];
                        $columnData = explode('|', $columnName);
                        $columnData = $this->prepareAttributeData($columnData);
                        $attribute = $this->attributeFactory->create();
                        $attribute->loadByCode(\Magento\Catalog\Model\Product::ENTITY, $columnData['attribute_code']);
                        if (!$attribute->getId()) {
                            $this->prepareAttributesData($columnData);
                            $attribute->setBackendType(
                                $attribute->getBackendTypeByInput($columnData['frontend_input'])
                            );
                            $defaultValueField = $attribute->getDefaultValueByInput($columnData['frontend_input']);
                            if (!$defaultValueField && isset($columnData['default_value'])) {
                                unset($columnData['default_value']);
                            }
                            $columnData['source_model'] = $this->productHelper->getAttributeSourceModelByInputType(
                                $columnData['frontend_input']
                            );
                            $columnData['backend_model'] = $this->productHelper->getAttributeBackendModelByInputType(
                                $columnData['frontend_input']
                            );

                            $attribute->addData($columnData);
                            try {
                                $attribute->save();
                            } catch (Exception $e) {
                                $invalidColumns[] = $columnName;
                            }
                            $attrSetCodes = explode(',', $columnData[self::ATTRIBUTE_SET_COLUMN]);
                            foreach ($attrSetCodes as $attrSetCode) {
                                if (isset($this->_attrSetNameToId[$attrSetCode])) {
                                    $attrSetId = $this->_attrSetNameToId[$attrSetCode];
                                    $attrGroupCode = isset($columnData[self::ATTRIBUTE_SET_GROUP])
                                        ? $columnData[self::ATTRIBUTE_SET_GROUP] : 'product-details';
                                    if (!isset($this->_attributeSetGroupCache[$attrSetId])) {
                                        $groupCollection =
                                            $this->groupCollectionFactory->create()->setAttributeSetFilter(
                                                $attrSetId
                                            )->load();
                                        foreach ($groupCollection as $group) {
                                            $attributeGroupCode = $group->getAttributeGroupCode();
                                            $this->_attributeSetGroupCache[$attrSetId][$attributeGroupCode] =
                                                $group->getAttributeGroupId();
                                        }
                                    }
                                    foreach ($this->_attributeSetGroupCache[$attrSetId] as $groupCode => $groupId) {
                                        if ($groupCode == $attrGroupCode) {
                                            $attribute->setAttributeSetId($attrSetId);
                                            $attribute->setAttributeGroupId($groupId);
                                            try {
                                                $attribute->save();
                                                $attrCodes[] = $attribute->getAttributeCode();
                                            } catch (Exception $e) {
                                                $this->addLogWriteln($e->getMessage(), $this->output, 'error');
                                            }
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        $this->_specialAttributes = array_merge($this->_specialAttributes, $attrCodes);
                    }

                    if (trim($columnName) == '') {
                        $emptyHeaderColumns[] = $columnNumber;
                    } elseif ($this->needColumnCheck && !in_array($columnName, $this->getValidColumnNames())) {
                        $invalidAttributes[] = $columnName;
                    }
                }
            }

            $this->addErrors(self::ERROR_CODE_INVALID_ATTRIBUTE, $invalidAttributes);
            $this->addErrors(self::ERROR_CODE_COLUMN_EMPTY_HEADER, $emptyHeaderColumns);
            $this->addErrors(self::ERROR_CODE_COLUMN_NAME_INVALID, $invalidColumns);
            $this->addLogWriteln(__('Finish checking columns'), $this->output);
            $this->addLogWriteln(__('Errors count: %1', $this->getErrorAggregator()->getErrorsCount()), $this->output);
            if (!$this->getErrorAggregator()->getErrorsCount()) {
                if ($saveBunches) {
                    $this->addLogWriteln(__('Start saving bunches'), $this->output);
                    $this->mergeFieldsMap();

                    $platform = null;
                    $platformName = $this->_parameters['platforms'] ?? null;
                    if ($platformName && is_string($platformName) && $platformName != 'magento2') {
                        $platform = $this->helper->getPlatformModel($platformName);
                    }
                    /* use platform @todo remove this */
                    if ($platform && method_exists($platform, 'saveValidatedBunches')) {
                        $platform->saveValidatedBunches(
                            $this->_getSource(),
                            $this->_resourceHelper->getMaxDataSize(),
                            $this->_importExportData->getBunchSize(),
                            $this->_dataSourceModel,
                            $this->_parameters,
                            $this->getEntityTypeCode(),
                            $this->getBehavior(),
                            $this->_processedRowsCount,
                            $this->getMultipleValueSeparator(),
                            $this
                        );
                        $this->_processedRowsCount = $platform->getProcessedRowsCount();
                    } else {
                        $this->_saveValidatedBunches();
                    }

                    $this->addLogWriteln(__('Finish saving bunches'), $this->output);
                    $this->_dataValidated = true;
                }
            }
        }

        return $this->getErrorAggregator();
    }

    /**
     * @return $this
     * @throws LocalizedException
     */
    protected function _initTypeModels()
    {
        $this->_importConfig = $this->fireImportConfig;
        $productTypes = $this->_importConfig->getEntityTypes($this->getEntityTypeCode());
        foreach ($productTypes as $productTypeName => $productTypeConfig) {
            $class = $productTypeConfig['model'];
            $class::$commonAttributesCache = [];
            $class::$attributeCodeToId = [];
        }
        try {
            parent::_initTypeModels();
        } catch (\Exception $exception) {
            $this->addLogWriteln($exception->getMessage(), $this->getOutput(), 'error');
            throw new LocalizedException(__($exception->getMessage()));
        }

        return $this;
    }

    /**
     * Checks and sets appropriate data for swatch attribute
     *
     * @param array $data
     */
    protected function prepareAttributesData(&$data)
    {
        if (isset($data['frontend_input'])) {
            switch ($data['frontend_input']) {
                case 'swatch_visual':
                    $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_VISUAL;
                    $data['frontend_input'] = 'select';
                    break;
                case 'swatch_text':
                    $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_TEXT;
                    $data['use_product_image_for_swatch'] = 0;
                    $data['frontend_input'] = 'select';
                    break;
                case 'select':
                    $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_DROPDOWN;
                    $data['frontend_input'] = 'select';
                    break;
            }
        }
    }

    /**
     * mergeFieldsMap function
     */
    protected function mergeFieldsMap()
    {
        if (isset($this->_parameters['map'])) {
            $newAttributes = [];
            foreach ($this->_parameters['map'] as $field) {
                if (!$field['import'] || !empty($field['import'])) {
                    $field['import'] = $field['system'];
                }
                $attribute = $this->getResource()->getAttribute($field['system']);
                if ($attribute) {
                    $newAttributes[$attribute->getAttributeCode()] = $field['import'];
                } else {
                    $newAttributes[$field['system']] = $field['import'];
                }
            }

            $this->_fieldsMap = array_merge($this->_fieldsMap, $newAttributes);
        }
    }

    /**
     * Validate data row
     *
     * @param array $rowData
     * @param int $rowNum
     * @return boolean
     */
    public function validateRow(array $rowData, $rowNum)
    {
        if (isset($rowData['origin_row_number'])) {
            $rowNum = $rowData['origin_row_number'] ?? $rowNum;
        }
        if (isset($this->_validatedRows[$rowNum])) {
            unset($this->_validatedRows[$rowNum]);
        }

        if (empty($this->_parameters['generate_url']) &&
            !empty($rowData[self::URL_KEY]) &&
            !empty($rowData[self::COL_SKU])
        ) {
            $sku = mb_strtolower($rowData[self::COL_SKU]);
            $urlKey = $rowData[self::URL_KEY];
            $urlKeys = $this->urlKeyManager->getUrlKeys();
            $storeId = $this->getRowStoreId($rowData);
            if (isset($urlKeys[$storeId][$urlKey]) && $urlKeys[$storeId][$urlKey] != $sku) {
                $message = sprintf(
                    $this->retrieveMessageTemplate(ValidatorInterface::ERROR_DUPLICATE_URL_KEY),
                    $rowData[self::URL_KEY],
                    $urlKeys[$storeId][$urlKey]
                );

                $this->addRowError($message, $rowNum);
                $this->getErrorAggregator()
                    ->addRowToSkip($rowNum);
            }
            $this->urlKeyManager->addUrlKeys($sku, $urlKey, $storeId);
        }

        return parent::validateRow($rowData, $rowNum);
    }

    /**
     * @return $this|MagentoProduct
     * @throws LocalizedException
     * @throws Exception
     */
    protected function _saveValidatedBunches()
    {
        $_currentRowSkus = [];
        $source = $this->_getSource();
        $currentDataSize = 0;
        $bunchRows = [];
        $startNewBunch = false;
        $nextRowBackup = [];
        $maxDataSize = $this->_resourceHelper->getMaxDataSize();
        $bunchSize = $this->_importExportData->getBunchSize();
        $skuSet = [];
        $file = null;
        $jobId = null;
        if (isset($this->_parameters['file'])) {
            $file = $this->_parameters['file'];
            $this->_dataSourceModel->setFile($file);
        }
        if (isset($this->_parameters['job_id'])) {
            $jobId = $this->_parameters['job_id'];
            $this->_dataSourceModel->setJobId($jobId);
        }
        $this->cache->clean(ImportProductCache::ROW_SKUS_CACHE_ID . $jobId);
        $source->rewind();
        $this->_dataSourceModel->cleanBunches();

        while ($source->valid() || $bunchRows) {
            if ($startNewBunch || !$source->valid()) {
                if (!empty($this->_parameters['use_only_fields_from_mapping'])) {
                    $this->useOnlyFieldsFromMapping($bunchRows, true);
                }
                if (empty($this->_parameters['generate_url'])) {
                    $bunchRows = $this->findUrlKeyDuplicates($bunchRows);
                }
                $this->addLogWriteln(__('Saving Validated Bunches'), $this->output, 'info');

                $this->_dataSourceModel->saveBunches(
                    $this->getEntityTypeCode(),
                    $this->getBehavior(),
                    $jobId,
                    $file,
                    $bunchRows
                );
                $bunchRows = $nextRowBackup;
                $currentDataSize = strlen($this->getSerializer()->serialize($bunchRows));
                $startNewBunch = false;
                $nextRowBackup = [];
            }
            if ($source->valid()) {
                $rowData = $source->current();
                $colStoreViewCode = $rowData[self::COL_STORE_VIEW_CODE] ?? '';
                $storeViews = explode($this->getMultipleValueSeparator(), $colStoreViewCode);
                foreach ($storeViews as $storeView) {
                    $rowKey = count($bunchRows);
                    $rowData[self::COL_STORE_VIEW_CODE] = $storeView;
                    try {
                        $this->currentSku = $rowData[self::COL_SKU];
                        $isCached = $this->_parameters['cache_products'] ?? false;
                        if ($isCached) {
                            $currentRowHash = sha1(trim(implode('', $rowData)));
                            $cache = $this->cache->load($currentRowHash);
                            if ($cache) {
                                $_currentRowSkus[] = mb_strtolower($rowData[self::COL_SKU]);
                                $this->addLogWriteln(
                                    __('Product %1 has not changed', $rowData['sku']),
                                    $this->output,
                                    'info'
                                );
                                $source->next();
                                continue 2;
                            }
                            $this->originalImportRows[strtolower($rowData['sku'])][] = $currentRowHash;
                        }
                        if (array_key_exists('sku', $rowData)) {
                            $skuSet[$rowData['sku']] = true;
                        }
                        $rowData = $this->getBundleSpecialAttributeMap($rowData);
                        $invalidAttr = [];
                        foreach ($rowData as $attrName => $element) {
                            if (is_string($element)) {
                                if (!mb_check_encoding($element, 'UTF-8')) {
                                    unset($rowData[$attrName]);
                                    $invalidAttr[] = $attrName;
                                }
                            }
                        }
                        if (!empty($invalidAttr)) {
                            $this->addRowError(
                                AbstractEntity::ERROR_CODE_ILLEGAL_CHARACTERS,
                                $this->_processedRowsCount,
                                implode(',', $invalidAttr)
                            );
                        }
                    } catch (InvalidArgumentException $e) {
                        $this->addRowError($e->getMessage(), $this->_processedRowsCount);
                        $this->_processedRowsCount++;
                        $source->next();
                        continue;
                    }
                    $rowData = $this->helper->trimArrayValues($rowData);
                    if (isset($rowData['configurable_variations']) && $rowData['configurable_variations']) {
                        $this->checkAttributePresenceInAttributeSet($rowData);
                    }
                    $rowData[self::COL_SKU] = $this->getCorrectSkuAsPerLength($rowData);
                    $_currentRowSkus[] = mb_strtolower($rowData[self::COL_SKU]);
                    $rowData = $this->customFieldsMapping($rowData);
                    $rowData = $this->customBunchesData($rowData);

                    $this->_processedRowsCount++;

                    $productSku = strtolower($this->getCorrectSkuAsPerLength($rowData));
                    $oldSkus = $this->skuProcessor->getOldSkus();
                    if ($this->onlyUpdate || $this->onlyAdd) {
                        if (!isset($oldSkus[$productSku]) && $this->onlyUpdate) {
                            $source->next();
                            continue 2;
                        } elseif (isset($oldSkus[$productSku]) && $this->onlyAdd) {
                            $source->next();
                            continue 2;
                        }
                    }

                    if ($this->onlyUpdate && empty($this->_parameters['clear_attribute_value'])) {
                        foreach ($rowData as $key => $value) {
                            if ('' === $value) {
                                unset($rowData[$key]);
                            }
                        }
                    }

                    if ($this->getBehavior() == Import::BEHAVIOR_REPLACE) {
                        if (isset($rowData['attribute_set_code'])) {
                            $rowData['_attribute_set'] = $rowData['attribute_set_code'];
                        }
                    }

                    /* specify url_key for to avoid product repository load
                    in \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver*/
                    if ((empty($rowData[self::URL_KEY]) && empty($oldSkus[$productSku])) ||
                        !empty($this->_parameters['enable_product_url_pattern'])
                    ) {
                        $rowData[self::URL_KEY] = $this->onlyUpdate ? '' : $this->getProductUrlKey($rowData);
                    }
                    if (empty($oldSkus[$productSku]) || !empty($rowData[self::URL_KEY])) {
                        $rowData = $this->processUrlKey($rowData);
                    }
                    if ($this->validateRow($rowData, $source->key())) {
                        // add row to bunch for save
                        $rowData = $this->_prepareRowForDb($rowData);
                        $rowSize = strlen($this->getSerializer()->serialize($rowData));

                        $isBunchSizeExceeded = $bunchSize > 0 && count($bunchRows) >= $bunchSize;

                        if (isset($rowData[self::COL_TYPE]) && !$this->validateRowByProductType($rowData, $rowKey)) {
                            $this->addRowError(ValidatorInterface::ERROR_TYPE_UNSUPPORTED, $rowKey);
                        }

                        if (($rowData['sku'] !== $this->getLastSku()) &&
                            ($currentDataSize + $rowSize >= $maxDataSize || $isBunchSizeExceeded)) {
                            $startNewBunch = true;
                            $nextRowBackup = [$rowKey => $rowData];
                        } else {
                            $rowData['origin_row_number'] = $source->key();
                            $bunchRows[$rowKey] = $rowData;
                            $currentDataSize += $rowSize;
                        }
                        $this->setLastSku($rowData['sku']);
                    }
                }

                $source->next();
            }
        }
        if (!empty($this->_parameters['configurable_switch']) &&
            empty($this->_parameters['configurable_create']) &&
            !empty($_currentRowSkus)
        ) {
            $this->cache->save(
                $this->getSerializer()->serialize($_currentRowSkus),
                ImportProductCache::ROW_SKUS_CACHE_ID . $jobId
            );
        }
        $this->updateExistingProductNotInImportList($_currentRowSkus);
        $this->getOptionEntity()->validateAmbiguousData();
        $this->_processedEntitiesCount = (count($skuSet)) ?: $this->_processedRowsCount;

        $this->cache->clean([ImportProductCache::BUFF_CACHE]);
        $this->cache->save(
            $this->getSerializer()->serialize($this->originalImportRows),
            sha1(ImportProductCache::BUFF_CACHE)
        );

        return $this;
    }

    /**
     * @inheritDoc
     */
    public function addRowError(
        $errorCode,
        $errorRowNum,
        $colName = null,
        $errorMessage = null,
        $errorLevel = ProcessingError::ERROR_LEVEL_CRITICAL,
        $errorDescription = null
    ) {
        $messageTemplate = ($errorMessage) ?: $this->retrieveMessageTemplate($errorCode);
        if ($messageTemplate && $this->currentSku) {
            $errorMessage = $messageTemplate . '. For SKU: ' . $this->currentSku;
        }
        return parent::addRowError($errorCode, $errorRowNum, $colName, $errorMessage, $errorLevel, $errorDescription);
    }

    /**
     * @param $rowData
     * @return array
     * @throws Exception
     */
    public function processUrlKey($rowData)
    {
        if ($this->isSkuExist($rowData[self::COL_SKU]) &&
            (isset($rowData[self::URL_KEY]) && !$rowData[self::URL_KEY])) {
            $rowData[self::URL_KEY] = $this->urlKeyManager
                ->getUrlKeyForSku($rowData[self::COL_SKU]);
        }
        if ($this->_parameters['generate_url'] == 1
            && $this->isNeedToChangeUrlKey($rowData)
        ) {
            $rowData = $this->generateUrlKey($rowData);
        }
        return $rowData;
    }

    /**
     * Update existing products that are not in the product import list
     *
     * @param array $_currentRowSkus
     * @throws Exception
     */
    public function updateExistingProductNotInImportList(array $_currentRowSkus)
    {
        $actionForExistingProducts = $this->_parameters['update_existing_products'] ?? false;
        $productIdsForDisable = $this->getProductIdsAccordingSupplierAttribute($_currentRowSkus);
        if (!empty($actionForExistingProducts) && $actionForExistingProducts !== StockStatusOptions::NOT_SET) {
            $productIds = $this->getExistProductIdsNotInImportList($_currentRowSkus);
            if ($actionForExistingProducts == StockStatusOptions::DISABLE_PRODUCT && !empty($productIds)) {
                $this->disableExistingProducts($productIds);
            } else {
                $this->updateInventoryStockItems($productIds, $actionForExistingProducts);
            }
        }
        $this->disableExistingProducts($productIdsForDisable);
    }

    /**
     * Checking attribute presence in attribute set
     *
     * @param $rowData
     */
    protected function checkAttributePresenceInAttributeSet($rowData)
    {
        $attributeCodes = [];
        $allAttributesInAttributeSet = [];
        $variations = explode(self::PSEUDO_MULTI_LINE_SEPARATOR, $rowData['configurable_variations']);
        $select = $this->_connection->select()
            ->from($this->getResource()->getTable('eav_attribute_set'))
            ->reset(Select::COLUMNS)
            ->columns('attribute_set_id')
            ->where('attribute_set_name=(?)', $rowData['attribute_set_code'])
            ->where('entity_type_id=(?)', $this->_entityTypeId);
        $attributeSetId = $this->_connection->fetchRow($select);
        foreach ($variations as $variation) {
            $fieldAndValuePairsText = explode($this->getMultipleValueSeparator(), $variation);
            foreach ($fieldAndValuePairsText as $nameAndValue) {
                $nameAndValue = explode(self::PAIR_NAME_VALUE_SEPARATOR, $nameAndValue);
                if (!empty($nameAndValue)) {
                    $attributeCodes[] = trim($nameAndValue[0]);
                }
            }
        }
        $attributeCodes = array_unique($attributeCodes);
        $select = $this->_connection->select()
            ->from(['eea' => $this->getResource()->getTable('eav_entity_attribute')])
            ->join(
                ['ea' => $this->getResource()->getTable('eav_attribute')],
                'eea.' . 'attribute_id' . ' = ea.' . 'attribute_id'
            )
            ->reset(Select::COLUMNS)
            ->columns('ea.attribute_code')
            ->where('eea.attribute_set_id=?', $attributeSetId['attribute_set_id'])
            ->where('eea.entity_type_id=?', $this->_entityTypeId);
        $attributeCodesInAttributeSet = array_values($this->_connection->fetchAll($select));
        foreach ($attributeCodesInAttributeSet as $attrCode) {
            $allAttributesInAttributeSet[] = $attrCode['attribute_code'];
        }
        foreach ($attributeCodes as $attributeCode) {
            if ($attributeCode == 'default') {
                continue;
            }
            if (!in_array($attributeCode, $allAttributesInAttributeSet)) {
                $this->addLogWriteln(
                    __(
                        "Not all products can be attached to a configurable product with sku = '%1',
                        since attribute '%2' is missing in attribute set '%3'.",
                        $rowData['sku'],
                        $attributeCode,
                        $rowData['attribute_set_code']
                    ),
                    $this->output,
                    'warning'
                );
            }
        }
    }

    /**
     * @param $sku
     *
     */
    public function setLastSku($sku)
    {
        $this->lastSku = $sku;
    }

    /**
     * @return mixed
     */
    public function getLastSku()
    {
        return $this->lastSku;
    }

    /**
     * @return string[]
     */
    public function getSpecialAttributes()
    {
        return $this->_specialAttributes;
    }

    /**
     * @return array
     */
    public function getAddFields()
    {
        return $this->addFields;
    }

    /**
     * @param string $productSku
     *
     * @return array
     */
    public function getProductWebsites($productSku)
    {
        return isset($this->websitesCache[$productSku]) ? array_keys($this->websitesCache[$productSku]) : [];
    }

    /**
     * @param string $productSku
     *
     * @return array
     */
    public function getProductCategories($productSku)
    {
        return isset($this->categoriesCache[$productSku]) ? array_keys($this->categoriesCache[$productSku]) : [];
    }

    /**
     * @return array
     */
    public function getNotValidSkus()
    {
        return $this->notValidedSku;
    }

    public function setErrorMessages()
    {
        $this->_initErrorTemplates();
    }

    /**
     * @return AbstractType
     */
    public function getSourceType()
    {
        return $this->sourceType;
    }

    /**
     * @return ProductMetadataInterface
     */
    public function getProductMetadata()
    {
        return $this->productMetadata;
    }

    /**
     * Parse values of multiselect attributes depends on "Fields Enclosure" parameter
     *
     * @param string $values
     * @param string $delimiter
     *
     * @return array
     * @since 100.1.2
     */
    public function parseMultiselectValues($values, $delimiter = self::PSEUDO_MULTI_LINE_SEPARATOR)
    {
        if ($delimiter == self::PSEUDO_MULTI_LINE_SEPARATOR
            && isset($this->_parameters['_import_multiple_value_separator'])
            && $this->_parameters['_import_multiple_value_separator']
        ) {
            $delimiter = $this->_parameters['_import_multiple_value_separator'];
        }

        $delimiter = $this->separatorFormatter->format($delimiter);
        $values = parent::parseMultiselectValues($values, $delimiter);

        if (is_array($values) && !empty($values)) {
            $values = $this->helper->trimArrayValues($values);
        }

        return array_unique(array_filter($values));
    }

    /**
     * @return mixed
     */
    public function getIsRowCategoryMapped()
    {
        return $this->_isRowCategoryMapped;
    }

    /**
     * @param mixed $isRowCategoryMapped
     */
    public function setIsRowCategoryMapped($isRowCategoryMapped)
    {
        $this->_isRowCategoryMapped = $isRowCategoryMapped;
    }

    /**
     * Retrieving images from all columns and rows
     *
     * @param $bunch
     *
     * @return array
     */
    protected function getBunchImages(
        $bunch
    ) {
        $allImagesFromBunch = [];
        foreach ($bunch as $rowData) {
            $rowData = $this->customFieldsMapping($rowData);
            foreach ($this->_imagesArrayKeys as $image) {
                if (empty($rowData[$image])) {
                    continue;
                }
                $dispersionPath =
                    \Magento\Framework\File\Uploader::getDispretionPath($rowData[$image]);
                $importImages = explode($this->getMultipleValueSeparator(), $rowData[$image]);
                foreach ($importImages as $importImage) {
                    $imageSting = mb_strtolower(
                        $dispersionPath . '/' . preg_replace('/[^a-z0-9\._-]+/i', '', $importImage)
                    );
                    /**
                     * TODO: check source type 'file'.
                     * Compare code with default Magento\CatalogImportExport\Model\Import\Product
                     */
                    if (isset($this->_parameters['import_source']) && $this->_parameters['import_source'] != 'file') {
                        $allImagesFromBunch[$this->sourceType->getCode() . $imageSting] = $imageSting;
                    } else {
                        $allImagesFromBunch[$importImage] = $imageSting;
                    }
                }
            }
        }

        return $allImagesFromBunch;
    }

    /**
     * @param $rowData
     *
     * @return mixed
     */
    protected function adjustBundleTypeAttributes($rowData)
    {
        if (isset($rowData['product_type']) && $rowData['product_type'] == 'bundle') {
            $fields = ['price_type', 'weight_type', 'sku_type'];
            foreach ($fields as $field) {
                if (isset($rowData[$field]) && (is_int($rowData[$field]) ||
                        in_array(
                            $rowData[$field],
                            [BundlePrice::PRICE_TYPE_DYNAMIC, BundlePrice::PRICE_TYPE_FIXED]
                        ))
                ) {
                    if ($rowData[$field] === Bundle::VALUE_DYNAMIC) {
                        $rowData[$field] = Bundle::VALUE_DYNAMIC;
                    } elseif ($rowData[$field] === BundlePrice::PRICE_TYPE_DYNAMIC) {
                        $rowData[$field] = Bundle::VALUE_DYNAMIC;
                    } else {
                        $rowData[$field] = Bundle::VALUE_FIXED;
                    }
                }
            }
        }

        return $rowData;
    }

    /**
     * Obtain scope of the row from row data.
     *
     * @param array $rowData
     *
     * @return int
     */
    public function getRowScope(array $rowData)
    {
        if (empty($rowData[self::COL_STORE])
            || $rowData[self::COL_STORE] == 'default'
        ) {
            return self::SCOPE_DEFAULT;
        }
        return self::SCOPE_STORE;
    }

    /**
     * @param array $rowData
     *
     * @return mixed
     */
    public function getCorrectSkuAsPerLength(array $rowData)
    {
        if (!empty($rowData[self::COL_SKU])) {
            $sku = (string) $rowData[self::COL_SKU];
        } else {
            $sku = '';
        }
        return mb_strlen($sku) > Sku::SKU_MAX_LENGTH ?
            mb_substr($sku, 0, Sku::SKU_MAX_LENGTH) : $sku;
    }

    /**
     * Get product entity link field
     *
     * @return string
     * @throws Exception
     */
    protected function getProductEntityLinkField()
    {
        if (!$this->productEntityLinkField) {
            $this->productEntityLinkField = $this->getMetadataPool()
                ->getMetadata(ProductInterface::class)
                ->getLinkField();
        }
        return $this->productEntityLinkField;
    }

    /**
     * Update and insert data in entity table.
     *
     * @param array $entityRowsIn Row for insert
     * @param array $entityRowsUp Row for update
     * @return $this
     * @throws ConnectionException
     * @throws DeadlockException
     * @throws LockWaitException
     * @throws Exception
     */
    public function saveProductEntity(array $entityRowsIn, array $entityRowsUp)
    {
        static $entityTable = null;

        if (!$entityTable) {
            $entityTable = $this->getResource()->getEntityTable();
        }
        if ($entityRowsUp) {
            $this->countItemsUpdated += count($entityRowsUp);

            $triesCount = 0;
            do {
                $retry = false;
                try {
                    $this->_connection->insertOnDuplicate(
                        $entityTable,
                        $entityRowsUp,
                        ['type_id', 'updated_at', 'attribute_set_id']
                    );
                } catch (Exception $e) {
                    if ((($e instanceof ConnectionException) ||
                            ($e instanceof DeadlockException) ||
                            ($e instanceof LockWaitException)) &&
                        $triesCount < self::MAX_DB_RETRIES) {
                        $retry = true;
                        $triesCount++;
                        $this->getLogger()->critical($e);
                        sleep(pow($triesCount, 2));
                    } else {
                        throw $e;
                    }
                }
            } while ($retry);
        }

        $entityRowsUp = [];

        try {
            $this->_connection->beginTransaction();
            parent::saveProductEntity($entityRowsIn, $entityRowsUp);
            $this->_connection->commit();
        } catch (Exception $e) {
            $this->_connection->rollBack();
            throw $e;
        }

        return $this;
    }

    /**
     * Validate data row.
     *
     * @param array $rowData
     * @param int $rowNum
     *
     * @return boolean
     */
    public function validateRowByProductType($rowData, $rowNum)
    {
        $sku = $rowData[self::COL_SKU];
        $newSku = $this->skuProcessor->getNewSku($sku);

        if (!isset($this->_productTypeModels[$rowData[self::COL_TYPE]])) {
            $this->addLogWriteln(
                __('Product type is not supported %1 ' . '. For SKU: %2', $rowData[self::COL_TYPE], $sku),
                $this->output,
                'error'
            );
            return false;
        }
        if ($newSku && ($newSku['type_id'] !== $rowData[self::COL_TYPE])) {
            $productTypeValidator = $this->_productTypeModels[$rowData[self::COL_TYPE]];
            $productTypeValidator->isRowValid(
                $rowData,
                $rowNum,
                !($this->isSkuExist($rowData[self::COL_SKU]) && Import::BEHAVIOR_REPLACE !== $this->getBehavior())
            );
            return !$this->getErrorAggregator()->isRowInvalid($rowNum);
        }
        return true;
    }

    /**
     * Check if product exists for specified SKU
     *
     * @param string $sku
     *
     * @return bool
     */
    protected function isSkuExist($sku)
    {
        if (version_compare($this->productMetadata->getVersion(), '2.2.0', '>=')) {
            $sku = strtolower($sku);
        }
        return isset($this->_oldSku[$sku]);
    }

    /**
     * Get existing product data for specified SKU
     *
     * @param string $sku
     *
     * @return array
     */
    protected function getExistingSku($skuValue)
    {
        $sku = (string)$skuValue;
        if (version_compare($this->productMetadata->getVersion(), '2.2.0', '>=')) {
            $sku = strtolower($sku);
        }
        if (isset($this->_oldSku[$sku])) {
            $result = $this->_oldSku[$sku];
        } else {
            $result = false;
        }
        return $result;
    }

    /**
     * @return $this|MagentoProduct
     * @throws LocalizedException
     * @throws Exception
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    protected function _saveLinks()
    {
        $resource = $this->_linkFactory->create();
        $mainTable = $resource->getMainTable();
        $positionAttrId = [];
        $nextLinkId = $this->_resourceHelper->getNextAutoincrement($mainTable);

        foreach ($this->_linkNameToId as $linkName => $linkId) {
            $select = $this->_connection->select()->from(
                $resource->getTable('catalog_product_link_attribute'),
                ['id' => 'product_link_attribute_id']
            )->where(
                'link_type_id = :link_id AND product_link_attribute_code = :position'
            );
            $bind = [':link_id' => $linkId, ':position' => 'position'];
            $positionAttrId[$linkId] = $this->_connection->fetchOne($select, $bind);
        }
        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            $productIds = [];
            $linkRows = [];
            $positionRows = [];

            foreach ($bunch as $rowNum => $rowData) {
                if (!$this->isRowAllowedToImport($rowData, $rowNum)) {
                    continue;
                }

                $sku = $rowData[self::COL_SKU];

                $productId = $this->skuProcessor->getNewSku($sku)[$this->getProductEntityLinkField()];
                $productLinkKeys = [];
                $select = $this->_connection->select()->from(
                    $resource->getTable('catalog_product_link'),
                    ['id' => 'link_id', 'linked_id' => 'linked_product_id', 'link_type_id' => 'link_type_id']
                )->where(
                    'product_id = :product_id'
                );
                $bind = [':product_id' => $productId];
                foreach ($this->_connection->fetchAll($select, $bind) as $linkData) {
                    $linkKey = "{$productId}-{$linkData['linked_id']}-{$linkData['link_type_id']}";
                    $productLinkKeys[$linkKey] = $linkData['id'];
                }
                foreach ($this->_linkNameToId as $linkName => $linkId) {
                    $productIds[] = $productId;
                    if (isset($rowData[$linkName . 'sku'])) {
                        $linkSkus = explode($this->getMultipleValueSeparator(), (string) $rowData[$linkName . 'sku']);
                        $linkPositions = !empty($rowData[$linkName . 'position'])
                            ? explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'position'])
                            : [];
                        foreach ($linkSkus as $linkedSkuKey => $linkedSku) {
                            $linkedSku = trim($linkedSku);
                            if ((($this->skuProcessor->getNewSku($linkedSku) !== null) || $this->isSkuExist($linkedSku))
                                && strcasecmp($linkedSku, $sku) !== 0
                            ) {
                                $newSku = $this->skuProcessor->getNewSku($linkedSku);
                                if (!empty($newSku)) {
                                    $linkedId = $newSku['entity_id'];
                                } else {
                                    $linkedId = $this->getExistingSku($linkedSku)['entity_id'];
                                }

                                if ($linkedId == null) {
                                    $this->getLogger()->critical(
                                        new Exception(
                                            sprintf(
                                                'WARNING: Orphaned link skipped: From SKU %s (ID %d) to SKU %s, ' .
                                                'Link type id: %d',
                                                $sku,
                                                $productId,
                                                $linkedSku,
                                                $linkId
                                            )
                                        )
                                    );
                                    continue;
                                }

                                $linkKey = "{$productId}-{$linkedId}-{$linkId}";
                                if (empty($productLinkKeys[$linkKey])) {
                                    $productLinkKeys[$linkKey] = $nextLinkId;
                                }
                                if (!isset($linkRows[$linkKey])) {
                                    $linkRows[$linkKey] = [
                                        'link_id' => $productLinkKeys[$linkKey],
                                        'product_id' => $productId,
                                        'linked_product_id' => $linkedId,
                                        'link_type_id' => $linkId,
                                    ];
                                }
                                if (!empty($linkPositions[$linkedSkuKey]) &&
                                    $this->isLinkExists($linkRows, $productLinkKeys[$linkKey])) {
                                    $positionRows[] = [
                                        'link_id' => $productLinkKeys[$linkKey],
                                        'product_link_attribute_id' => $positionAttrId[$linkId],
                                        'value' => $linkPositions[$linkedSkuKey],
                                    ];
                                } elseif ($this->isLinkExists($linkRows, $productLinkKeys[$linkKey])) {
                                    $positionRows[] = [
                                        'link_id' => $productLinkKeys[$linkKey],
                                        'product_link_attribute_id' => $positionAttrId[$linkId],
                                        'value' => $linkedSkuKey + 1,
                                    ];
                                }
                                $nextLinkId++;
                            }
                        }
                    }
                }
            }
            if (Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) {
                $this->_connection->delete(
                    $mainTable,
                    $this->_connection->quoteInto('product_id IN (?)', array_unique($productIds))
                );
            }
            $this->savePreparedLinks($linkRows, $positionRows);
        }
        return $this;
    }

    /**
     * @param $links
     * @param $linkId
     *
     * @return bool
     */
    protected function isLinkExists($links, $linkId)
    {
        foreach ($links as $linkData) {
            if ($linkData['link_id'] == $linkId) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param $linkRows
     * @param $positionRows
     * @throws LocalizedException
     */
    protected function savePreparedLinks($linkRows, $positionRows)
    {
        $resource = $this->_linkFactory->create();
        $mainTable = $resource->getMainTable();
        if ($linkRows) {
            $this->_connection->insertOnDuplicate($mainTable, $linkRows, ['link_id']);
        }
        if ($positionRows) {
            $this->_connection->insertOnDuplicate(
                $resource->getAttributeTypeTable('int'),
                $positionRows,
                ['value']
            );
        }
    }

    /**
     * @return array
     */
    protected function getStoreIds()
    {
        if (empty($this->storeIds)) {
            $this->storeIds = array_merge(
                array_keys($this->getStoreManager()->getStores()),
                [0]
            );
        }
        return $this->storeIds;
    }

    /**
     * @return array
     */
    protected function getStoreWithCodes()
    {
        $stores = [];
        foreach ($this->getStoreManager()->getStores() as $store) {
            $stores[$store->getId()] = $store->getCode();
        }
        return $stores;
    }

    /**
     * Returns attributes all values in label-value or value-value pairs form. Labels are lower-cased.
     *
     * @param AbstractAttribute $attribute
     * @param array $indexValAttrs OPTIONAL Additional attributes' codes with index values.
     *
     * @return array
     */
    public function getAttributeOptions(AbstractAttribute $attribute, $indexValAttrs = [])
    {
        $options = [];
        $stores = $this->getStoreWithCodes();
        $stores[0] = 'admin'; // We add admin store here for backward compatibility
        foreach ($stores as $id => $code) {
            $attribute->setStoreId($id);
            $options[$code] = parent::getAttributeOptions($attribute, $indexValAttrs);
            try {
                if ($this->swatchesHelperData->isTextSwatch($attribute)) {
                    $swatchOptionsIds = array_values($options[$code]);
                    $swatchOption = $this->getSwatchesByOptionsId($swatchOptionsIds, $attribute->getAttributeId());
                    foreach ($swatchOption as $optionId => $optionValue) {
                        if (isset($optionValue['value'])
                            && ($optionValue['value'] !== '' || $optionValue['value'] !== null)
                        ) {
                            $options[$code][strtolower($optionValue['value'])] = $optionId;
                        }
                    }
                }
            } catch (Exception $e) {
                $this->addLogWriteln($e->getMessage(), $this->getOutput(), 'error');
            }
        }
        return $options;
    }

    /**
     * @param $rowData
     * @param $mediaGallery
     * @param $existingImages
     * @param $uploadedImages
     * @param $rowNum
     * @param $existingAttributeImages
     * @throws LocalizedException
     */
    protected function processMediaGalleryRows(
        &$rowData,
        &$mediaGallery,
        &$existingImages,
        &$uploadedImages,
        $rowNum,
        $existingAttributeImages
    ) {
        $this->importImageProcessor->processMediaGalleryRows(
            $rowData,
            $mediaGallery,
            $existingImages,
            $uploadedImages,
            $rowNum,
            $existingAttributeImages
        );
    }

    /**
     * @param $uploadedImages
     * @param $images
     * @return $this
     */
    protected function clearPartUploadedImages(&$uploadedImages, $images)
    {
        $this->importImageProcessor->clearPartUploadedImages($uploadedImages, $images);
        return $this;
    }

    /**
     * @return StoreManagerInterface
     */
    public function getStoreManager(): StoreManagerInterface
    {
        return $this->storeManager;
    }

    /**
     * @param $productId
     * @return array
     * @throws Exception
     */
    public function getCategoryLinks($productId)
    {
        $categoryLinkMetadata = $this->getMetadataPool()->getMetadata(CategoryLinkInterface::class);
        $select = $this->_connection->select();
        $select->from($categoryLinkMetadata->getEntityTable(), ['category_id', 'position']);
        $select->where('product_id = ?', $productId);
        $result = $this->_connection->fetchAll($select);
        return $result;
    }

    /**
     * @param $categoryIds
     * @return array
     * @throws Exception
     */
    public function getCategoryPosition($categoryIds)
    {
        $categoryLinkMetadata = $this->getMetadataPool()->getMetadata(CategoryLinkInterface::class);
        $select = $this->_connection->select();
        $select->from($categoryLinkMetadata->getEntityTable(), ['position']);
        $select->where('category_id in (?)', $categoryIds);
        $result = $this->_connection->fetchAll($select);
        return $result;
    }

    /**
     * Whether a url key is needed to be change.
     *
     * @param array $rowData
     * @return bool
     */
    protected function isNeedToChangeUrlKey(array $rowData): bool
    {
        $urlKey = $this->getUrlKey($rowData);
        $productExists = $this->isSkuExist($rowData[self::COL_SKU]);
        $markedToEraseUrlKey = isset($rowData[self::URL_KEY]);
        // The product isn't new and the url key index wasn't marked for change.
        if (!$urlKey && $productExists && !$markedToEraseUrlKey) {
            // Seems there is no need to change the url key
            return false;
        }
        return true;
    }

    /**
     * Check Already uploaded images against fileName to avoid upload
     *
     * @param $existingImages
     * @param $columnImage
     * @param $rowSku
     * @return array
     * @throws LocalizedException
     */
    protected function checkAlreadyUploadedImages(&$existingImages, $columnImage, $rowSku)
    {
        return $this->importImageProcessor->checkAlreadyUploadedImages($existingImages, $columnImage, $rowSku);
    }

    /**
     * @param array $mediaGalleryData
     * @return $this|MagentoProduct
     */
    protected function _saveMediaGallery(array $mediaGalleryData)
    {
        if (empty($mediaGalleryData)) {
            return $this;
        }

        if (!class_exists(MediaGalleryProcessor::class)) {
            parent::_saveMediaGallery($mediaGalleryData);
            return $this;
        }

        $this->mediaProcessor->saveMediaGallery($mediaGalleryData);

        return $this;
    }

    /**
     * Get existing images for current bunch
     *
     * @param array $bunch
     * @return array
     */
    protected function getExistingImages($bunch)
    {
        if (!class_exists(MediaGalleryProcessor::class)) {
            return parent::getExistingImages($bunch);
        }
        return $this->mediaProcessor->getExistingImages($bunch);
    }

    /**
     * @param $rowData
     * @return mixed
     */
    protected function getBundleSpecialAttributeMap($rowData)
    {
        if (isset($rowData['product_type']) && $rowData['product_type'] == 'bundle') {
            $fields = ['price_type', 'weight_type', 'sku_type'];
            foreach ($fields as $field) {
                if (isset($rowData[$field])) {
                    if ($rowData[$field] == BundlePrice::PRICE_TYPE_DYNAMIC) {
                        $rowData[$field] = Bundle::VALUE_DYNAMIC;
                    } else {
                        $rowData[$field] = Bundle::VALUE_FIXED;
                    }
                }
            }
        }

        return $rowData;
    }

    /**
     * @param array $rowData
     * @return array|int|string|null
     */
    protected function getRowStoreId($rowData)
    {
        $storeViewCode = $rowData[self::COL_STORE_VIEW_CODE] ?? Store::DEFAULT_STORE_ID;
        return $this->getStoreIdByCode($storeViewCode);
    }

    /**
     * Multiple categories value separator getter.
     *
     * @return string
     */
    public function getMultipleCategorySeparator()
    {
        if (!empty($this->_parameters['categories_separator'])) {
            return $this->_parameters['categories_separator'];
        }
        $jobId = $this->_dataSourceModel->getJobId();
        $jobRepo = $this->jobRepository->getById($jobId);
        if ($jobRepo->getBehaviorData()) {
            return $jobRepo->getBehaviorData()['categories_separator'];
        }

        return Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
    }

    /**
     * Get existing product ids according to selected supplier attribute
     *
     * @param $_currentRowSkus
     * @return array
     * @throws Exception
     */
    protected function getProductIdsAccordingSupplierAttribute($_currentRowSkus)
    {
        $attributeCode = $this->scopeConfig->getValue('firebear_importexport/general/supplier_code');
        $supplierValue = $this->_parameters['product_supplier'] ?? null;
        $productIds = [];
        if ($attributeCode && $supplierValue) {
            /** @var Collection $nonExistingProducts */
            $nonExistingProducts = $this->productCollectionFactory->create()
                ->setStoreId(0)
                ->addAttributeToSelect([$this->getProductIdentifierField(), self::COL_SKU, 'status', $attributeCode])
                ->addAttributeToFilter('status', ['neq' => Status::STATUS_DISABLED])
                ->addAttributeToFilter($attributeCode, $supplierValue)
                ->addFieldToFilter('sku', ['nin' => $_currentRowSkus])
                ->load();

            /** @var \Magento\Catalog\Model\Product $nonExistingProduct */
            foreach ($nonExistingProducts as $nonExistingProduct) {
                $productIds[] = $nonExistingProduct->getData($this->getProductIdentifierField());
            }
        }
        return $productIds;
    }

    /**
     * Get existing product ids that are not in the product import list
     *
     * @param $_currentRowSkus
     * @return array
     * @throws Exception
     */
    protected function getExistProductIdsNotInImportList($_currentRowSkus)
    {
        $actionForExistingProducts = $this->_parameters['update_existing_products'] ?? '';
        if ((int)$actionForExistingProducts !== StockStatusOptions::DISABLE_PRODUCT) {
            $productIds = [];
            $existingSkus = array_keys($this->getOldSku());
            $nonExistingSkus = array_diff($existingSkus, $_currentRowSkus);
            if (!empty($nonExistingSkus)) {
                foreach ($nonExistingSkus as $sku) {
                    if ($existingProductData = $this->getExistingSku($sku)) {
                        $productIds[] = $existingProductData[$this->getProductIdentifierField()];
                    }
                }
            }
            return $productIds;
        } else {
            $websites = $this->_parameters['disable_by_website_ids'] ?? '';
            if (empty($websites)) {
                $websites = [0];
            }

            $nonExistingSkus = [];
            foreach ($websites as $website) {
                $nonExistingSkus[$website] = [];

                $website = $this->storeManager->getWebsite($website);

                if ($website) {
                    $storeId = $website->getDefaultStore()->getId();
                } else {
                    $storeId = 0;
                }

                $products = $this->productCollectionFactory->create()
                    ->setStoreId($storeId)
                    ->addAttributeToSelect([$this->getProductIdentifierField(), self::COL_SKU, 'status'])
                    ->addAttributeToFilter('status', ['eq' => Status::STATUS_ENABLED])
                    ->addFieldToFilter('sku', ['nin' => $_currentRowSkus])
                    ->load();

                if (!empty($products)) {
                    foreach ($products as $product) {
                        $nonExistingSkus[$storeId][] = $product->getData($this->getProductIdentifierField());
                    }
                }
            }
        }
        return $nonExistingSkus;
    }

    /**
     * Disable existing products
     *
     * @param $productIds
     */
    protected function disableExistingProducts($productIds)
    {
        try {
            $data = [];
            if (!empty($productIds)) {
                $updateData = [
                    'status' => Status::STATUS_DISABLED,
                ];
                /** @var Action $productAction */
                $productAction = $this->productActionFactory->create();
                foreach ($productIds as $storeId => $productIdsData) {
                    if (!empty($productIdsData)) {
                        foreach ($productIdsData as $key => $productId) {
                            $data[] = $productId;
                        }
                        $productAction->updateAttributes($productIdsData, $updateData, $storeId);
                    }
                }
            }
            if (!empty($data)) {
                foreach ($this->getOldSku() as $sku => $productData) {
                    if (in_array($productData[$this->getProductIdentifierField()], $data)) {
                        $this->addLogWriteln(
                            __('Product with sku %1 was disabled', $sku),
                            $this->getOutput(),
                            'info'
                        );
                    }
                }
            }
        } catch (Exception $e) {
            $this->addLogWriteln($e->getMessage(), $this->getOutput(), 'error');
        }
    }

    /**
     * Update Inventory Stock Items
     *
     * @param array $productIds
     * @param $actionForExistingProducts
     */
    protected function updateInventoryStockItems(array $productIds, $actionForExistingProducts)
    {
        switch ($actionForExistingProducts) {
            case StockStatusOptions::OUT_OF_STOCK:
                $updateStockData = [
                    'is_in_stock' => 0
                ];
                $message = __('Product with sku %1 is set as \'Out of stock\'');
                break;
            case StockStatusOptions::ZERO_QTY:
                $updateStockData = [
                    'qty' => 0
                ];
                $message = __('Qty for product with sku %1 is set to 0');
                break;
            case StockStatusOptions::OUT_OF_STOCK_AND_ZERO_QTY:
                $updateStockData = [
                    'qty' => 0,
                    'is_in_stock' => 0,
                ];
                $message = __(
                    'Product with sku %1 is set as \'Out of stock\' and qty for the product is set to 0'
                );
                break;
        }
        if (!empty($updateStockData)) {
            try {
                $this->_connection->update(
                    'cataloginventory_stock_item',
                    $updateStockData,
                    [
                        'product_id' . ' IN (?)' => $productIds,
                        'website_id = ?' => self::ADMIN_WEBSITE_ID,
                    ]
                );
                foreach ($this->getOldSku() as $sku => $productData) {
                    if (!empty($message) && in_array($productData[$this->getProductIdentifierField()], $productIds)) {
                        $this->addLogWriteln(
                            __($message, $sku),
                            $this->getOutput(),
                            'info'
                        );
                    }
                }
            } catch (Exception $exception) {
                $this->addLogWriteln($exception->getMessage(), $this->getOutput(), 'info');
                $this->getLogger()->debug($exception);
            }
        }
    }
}