local skillTypes = {
	"Attack",
	"Spell",
	"Projectile",
	"DualWieldOnly",
	"Buff",
	"Minion",
	"Damage",
	"Area",
	"Duration",
	"RequiresShield",
	"ProjectileSpeed",
	"HasReservation",
	"ReservationBecomesCost",
	"Trappable",
	"Totemable",
	"Mineable",
	"ElementalStatus",
	"MinionsCanExplode",
	"Chains",
	"Melee",
	"MeleeSingleTarget",
	"Multicastable",
	"TotemCastsAlone",
	"Multistrikeable",
	"CausesBurning",
	"SummonsTotem",
	"TotemCastsWhenNotDetached",
	"Physical",
	"Fire",
	"Cold",
	"Lightning",
	"Triggerable",
	"Trapped",
	"Movement",
	"DamageOverTime",
	"RemoteMined",
	"Triggered",
	"Vaal",
	"Aura",
	"CanTargetUnusableCorpse",
	"RangedAttack",
	"Chaos",
	"FixedSpeedProjectile",
	"ThresholdJewelArea",
	"ThresholdJewelProjectile",
	"ThresholdJewelDuration",
	"ThresholdJewelRangedAttack",
	"Channel",
	"DegenOnlySpellDamage",
	"InbuiltTrigger",
	"Golem",
	"Herald",
	"AuraAffectsEnemies",
	"NoRuthless",
	"ThresholdJewelSpellDamage",
	"Cascadable",
	"ProjectilesFromUser",
	"MirageArcherCanUse",
	"ProjectileSpiral",
	"SingleMainProjectile",
	"MinionsPersistWhenSkillRemoved",
	"ProjectileNumber",
	"Warcry",
	"Instant",
	"Brand",
	"DestroysCorpse",
	"NonHitChill",
	"ChillingArea",
	"AppliesCurse",
	"CanRapidFire",
	"AuraDuration",
	"AreaSpell",
	"OR",
	"AND",
	"NOT",
	"AppliesMaim",
	"CreatesMinion",
	"Guard",
	"Travel",
	"Blink",
	"CanHaveBlessing",
	"ProjectilesNotFromUser",
	"AttackInPlaceIsDefault",
	"Nova",
	"InstantNoRepeatWhenHeld",
	"InstantShiftAttackForLeftMouse",
	"AuraNotOnCaster",
	"Banner",
	"Rain",
	"Cooldown",
	"ThresholdJewelChaining",
	"Slam",
	"Stance",
	"NonRepeatable",
	"OtherThingUsesSkill",
	"Steel",
	"Hex",
	"Mark",
	"Aegis",
	"Orb",
	"KillNoDamageModifiers",
	"RandomElement",
	"LateConsumeCooldown",
	"Arcane",
	"FixedCastTime",
	"RequiresOffHandNotWeapon",
	"Link",
	"Blessing",
	"ZeroReservation",
	"DynamicCooldown",
	"Microtransaction",
	"OwnerCannotUse",
	"ProjectilesNotFired",
	"TotemsAreBallistae",
	"SkillGrantedBySupport",
	"PreventHexTransfer",
	"MinionsAreUndamageable",
	"InnateTrauma",
	"DualWieldRequiresDifferentTypes",
	"NoVolley",
	"Retaliation",
	"NeverExertable",
	"DisallowTriggerSupports",
	"ProjectileCannotReturn",
	"Offering",
	"SupportedByBane",
}

-- This is here to fix name collisions like in the case of Barrage
local fullNameGems = {
	["Metadata/Items/Gems/SupportGemBarrage"] = true,
}

local function mapAST(ast)
	return "SkillType."..(skillTypes[ast._rowIndex] or ("Unknown"..ast._rowIndex))
end

local weaponClassMap = {
	["Claw"] = "Claw",
	["Dagger"] = "Dagger",
	["Wand"] = "Wand",
	["One Hand Sword"] = "One Handed Sword",
	["Thrusting One Hand Sword"] = "Thrusting One Handed Sword",
	["One Hand Axe"] = "One Handed Axe",
	["One Hand Mace"] = "One Handed Mace",
	["Bow"] = "Bow",
	["Fishing Rod"] = "Fishing Rod",
	["Staff"] = "Staff",
	["Two Hand Sword"] = "Two Handed Sword",
	["Two Hand Axe"] = "Two Handed Axe",
	["Two Hand Mace"] = "Two Handed Mace",
	["Sceptre"] = "Sceptre",
	["Unarmed"] = "None",
}

local skillStatScope = { }
do
	local text = convertUTF16to8(getFile("Metadata/StatDescriptions/skillpopup_stat_filters.txt"))
	for skillName, scope in text:gmatch('([%w_]+) "Metadata/StatDescriptions/([%w_]+)%.txt"') do
		skillStatScope[skillName] = scope
	end
	for skillName, copyFromSkill in text:gmatch('copy ([%w_]+) ([%w_]+)') do
		skillStatScope[skillName] = skillStatScope[copyFromSkill]
	end
end

local gems = { }
local trueGemNames = { }

local directiveTable = { }

-- #noGem
-- Disables the gem component of the next skill
directiveTable.noGem = function(state, args, out)
	state.noGem = true
end

-- #addSkillTypes <flag>[ <flag>[...]]
-- skill types to be added to the skillTypes flags for this active skill
directiveTable.addSkillTypes = function(state, args, out)
	state.addSkillTypes = {}
	for flag in args:gmatch("%a+") do
		table.insert(state.addSkillTypes, flag)
	end
end

-- #skill <GrantedEffectId> [<Display name>]
-- Initialises the skill data and emits the skill header
directiveTable.skill = function(state, args, out)
	local grantedId, displayName = args:match("(%w+) (.+)")
	if not grantedId then
		grantedId = args
		displayName = args
	end
	out:write('skills["', grantedId, '"] = {\n')
	local granted = dat("GrantedEffects"):GetRow("Id", grantedId)
	if not granted then
		ConPrintf('Unknown GE: "'..grantedId..'"')
		return
	end
	local gemEffect = dat("GemEffects"):GetRow("GrantedEffect", granted)
	local secondaryEffect
	if not gemEffect then
		gemEffect = dat("GemEffects"):GetRow("GrantedEffect2", granted)
		if gemEffect then 
			secondaryEffect = true
		end
	end
	local skillGem
	if gemEffect then
		for gem in dat("SkillGems"):Rows() do
			for _, variant in ipairs(gem.GemVariants) do
				if gemEffect.Id == variant.Id then
					skillGem = gem
					local trueGemNameObj = dat("GemEffects"):GetRow("Id", gemEffect.Id)
					if trueGemNameObj.Name ~= "" then
						trueGemNames[gemEffect.Id] = trueGemNameObj.Name
					end
					break
				end
			end
			if skillGem then break end
		end
	end
	local skill = { }
	state.skill = skill
	if skillGem and not state.noGem then
		gems[gemEffect.Id] = true
		if granted.IsSupport then
			out:write('\tname = "', fullNameGems[skillGem.BaseItemType.Id] and skillGem.BaseItemType.Name or skillGem.BaseItemType.Name:gsub(" Support",""), '",\n')
			if #gemEffect.Description > 0 then
				out:write('\tdescription = "', gemEffect.Description:gsub('\n','\\n'), '",\n')
			end
		else
			out:write('\tname = "', secondaryEffect and granted.ActiveSkill.DisplayName or trueGemNames[gemEffect.Id] or granted.ActiveSkill.DisplayName, '",\n')
			-- Hybrid gems (e.g. Vaal gems) use the display name of the active skill e.g. Vaal Summon Skeletons of Sorcery
			out:write('\tbaseTypeName = "', granted.ActiveSkill.DisplayName, '",\n')
		end
	else
		if displayName == args and not granted.IsSupport then
			displayName = gemEffect and trueGemNames[gemEffect.Id] or granted.ActiveSkill.DisplayName
		end
		out:write('\tname = "', displayName, '",\n')
		out:write('\thidden = true,\n')
	end
	state.noGem = false
	skill.baseFlags = { }
	local modMap = { }
	skill.mods = { }
	skill.levels = { }
	local statMap = { }
	skill.stats = { }
	skill.CannotGrantToMinion = { }
	skill.constantStats = { }
	skill.addSkillTypes = state.addSkillTypes
	state.addSkillTypes = nil
	out:write('\tcolor = ', granted.Attribute, ',\n')
	if granted.GrantedEffectStatSets.BaseEffectiveness ~= 1 then
		out:write('\tbaseEffectiveness = ', granted.GrantedEffectStatSets.BaseEffectiveness, ',\n')
	end
	if granted.GrantedEffectStatSets.IncrementalEffectiveness ~= 0 then
		out:write('\tincrementalEffectiveness = ', granted.GrantedEffectStatSets.IncrementalEffectiveness, ',\n')
	end
	if granted.IsSupport then
		skill.isSupport = true
		out:write('\tsupport = true,\n')
		out:write('\trequireSkillTypes = { ')
		for _, type in ipairs(granted.SupportTypes) do
			out:write(mapAST(type), ', ')
		end
		out:write('},\n')
		out:write('\taddSkillTypes = { ')
		skill.isTrigger = false
		for _, type in ipairs(granted.AddTypes) do
			local typeString = mapAST(type)
			if typeString == "SkillType.Triggered" then
				skill.isTrigger = true
			end
			out:write(typeString, ', ')
		end
		out:write('},\n')
		out:write('\texcludeSkillTypes = { ')
		for _, type in ipairs(granted.ExcludeTypes) do
			out:write(mapAST(type), ', ')
		end
		out:write('},\n')
		if skill.isTrigger then
			out:write('\tisTrigger = true,\n')
		end
		if granted.SupportGemsOnly then
			out:write('\tsupportGemsOnly = true,\n')
		end
		if granted.IgnoreMinionTypes then
			out:write('\tignoreMinionTypes = true,\n')
		end
		if granted.PlusVersionOf then
			out:write('\tplusVersionOf = "', granted.PlusVersionOf.Id, '",\n')
		end
		local weaponTypes = { }
		for _, class in ipairs(granted.WeaponRestrictions) do
			if weaponClassMap[class.Id] then
				weaponTypes[weaponClassMap[class.Id]] = true
			end
		end
		if next(weaponTypes) then
			out:write('\tweaponTypes = {\n')
			for type in pairs(weaponTypes) do
				out:write('\t\t["', type, '"] = true,\n')
			end
			out:write('\t},\n')
		end
		out:write('\tstatDescriptionScope = "gem_stat_descriptions",\n')
	else
		if #granted.ActiveSkill.Description > 0 then
			out:write('\tdescription = "', granted.ActiveSkill.Description:gsub('"','\\"'):gsub('\r',''):gsub('\n','\\n'), '",\n')
		end
		out:write('\tskillTypes = { ')
		for _, type in ipairs(granted.ActiveSkill.SkillTypes) do
			out:write('[', mapAST(type), '] = true, ')
		end
		if skill.addSkillTypes then
			for _, type in ipairs(skill.addSkillTypes) do
				out:write('[SkillType.', type , '] = true, ')
			end
		end
		out:write('},\n')
		if granted.ActiveSkill.MinionSkillTypes[1] then
			out:write('\tminionSkillTypes = { ')
			for _, type in ipairs(granted.ActiveSkill.MinionSkillTypes) do
				out:write('[', mapAST(type), '] = true, ')
			end
			out:write('},\n')
		end
		local weaponTypes = { }
		for _, class in ipairs(granted.ActiveSkill.WeaponRestrictions) do
			if weaponClassMap[class.Id] then
				weaponTypes[weaponClassMap[class.Id]] = true
			end
		end
		if next(weaponTypes) then
			out:write('\tweaponTypes = {\n')
			for type in pairs(weaponTypes) do
				out:write('\t\t["', type, '"] = true,\n')
			end
			out:write('\t},\n')
		end
		out:write('\tstatDescriptionScope = "', skillStatScope[granted.ActiveSkill.Id] or "skill_stat_descriptions", '",\n')
		if granted.ActiveSkill.SkillTotem <= 21 then
			out:write('\tskillTotemId = ', granted.ActiveSkill.SkillTotem, ',\n')
		end
		out:write('\tcastTime = ', granted.CastTime / 1000, ',\n')
		if granted.CannotBeSupported then
			out:write('\tcannotBeSupported = true,\n')
		end
	end
	local statsPerLevel = dat("GrantedEffectStatSetsPerLevel"):GetRowList("GrantedEffectStatSets", granted.GrantedEffectStatSets)
	local statMapOrder = {}
	local perLevel = dat("GrantedEffectsPerLevel"):GetRowList("GrantedEffect", granted)
	if #perLevel ~= #statsPerLevel and #perLevel > 1 and #statsPerLevel > 1 then
		ConPrintf("UNKNOWN CASE of Level to Stat rows for '" .. granted.Id .. "'")
	end
	for indx = 1, math.max(#perLevel, #statsPerLevel) do
		local levelRow = perLevel[indx] or perLevel[1]
		local statRow = statsPerLevel[indx] or statsPerLevel[1]
		local level = { extra = { }, statInterpolation = { }, cost = { } }
		level.level = #perLevel == 1 and statRow.GemLevel or levelRow.Level
		level.extra.levelRequirement = #perLevel == 1 and statRow.PlayerLevelReq or levelRow.PlayerLevelReq
		for i, cost in ipairs(levelRow.CostTypes) do
			level.cost[cost["Resource"]] = levelRow.CostAmounts[i]
		end
		if levelRow.ManaReservationFlat ~= 0 then
			level.extra.manaReservationFlat = levelRow.ManaReservationFlat
		end
		if levelRow.ManaReservationPercent ~= 0 then
			level.extra.manaReservationPercent = levelRow.ManaReservationPercent / 100
		end
		if levelRow.LifeReservationFlat ~= 0 then
			level.extra.lifeReservationFlat = levelRow.LifeReservationFlat
		end
		if levelRow.LifeReservationPercent ~= 0 then
			level.extra.lifeReservationPercent = levelRow.LifeReservationPercent / 100
		end
		if levelRow.CostMultiplier ~= 100 then
			level.extra.manaMultiplier = levelRow.CostMultiplier - 100
		end
		if levelRow.AttackSpeedMultiplier and levelRow.AttackSpeedMultiplier ~= 0 then
			level.extra.attackSpeedMultiplier = levelRow.AttackSpeedMultiplier
		end
		if levelRow.AttackTime ~= 0 then
			level.extra.attackTime = levelRow.AttackTime
		end
		if levelRow.Cooldown and levelRow.Cooldown ~= 0 then
			level.extra.cooldown = levelRow.Cooldown / 1000
		end
		if levelRow.PvPDamageMultiplier ~= 0 then
			level.extra.PvPDamageMultiplier = levelRow.PvPDamageMultiplier
		end
		if levelRow.StoredUses ~= 0 then
			level.extra.storedUses = levelRow.StoredUses
		end
		if levelRow.VaalSouls ~= 0 then
			level.cost.Soul = levelRow.VaalSouls
		end
		if levelRow.VaalStoredUses ~= 0 then
			level.extra.vaalStoredUses = levelRow.VaalStoredUses
		end
		if levelRow.SoulGainPreventionDuration ~= 0 then
			level.extra.soulPreventionDuration = levelRow.SoulGainPreventionDuration / 1000
		end
		-- stat based level info
		if statRow.DamageEffectiveness ~= 0 then
			level.extra.damageEffectiveness = statRow.DamageEffectiveness / 10000 + 1
		end
		if statRow.AttackCritChance ~= 0 then
			level.extra.critChance = statRow.AttackCritChance / 100
		end
		if statRow.OffhandCritChance ~= 0 then
			level.extra.critChance = statRow.OffhandCritChance / 100
		end
		if statRow.BaseMultiplier and statRow.BaseMultiplier ~= 0 then
			level.extra.baseMultiplier = statRow.BaseMultiplier / 10000 + 1
		end
		level.statInterpolation = statRow.StatInterpolations
		local resolveInterpolation = false
		local injectConstantValuesIntoEachLevel = false
		local statMapOrderIndex = 1
		for i, stat in ipairs(statRow.FloatStats) do
			if not statMap[stat.Id] or indx == 1 then
				statMap[stat.Id] = #skill.stats + 1
				table.insert(skill.stats, { id = stat.Id })
				if indx == 1 then
					table.insert(statMapOrder, stat.Id)
					if stat.CannotGrantToMinion then
						table.insert(skill.CannotGrantToMinion, stat.Id)
					end
				else
					print(displayName .. ": stat missing from earlier levels: ".. stat.Id)
				end
			elseif statMapOrder[statMapOrderIndex] ~= stat.Id then
				-- add missing stats
				while statMapOrderIndex < #statMapOrder and statMapOrder[statMapOrderIndex] ~= stat.Id do
					table.insert(level, 0)
					if #level.statInterpolation < #statMapOrder then
						table.insert(level.statInterpolation, statMapOrderIndex, "0")
					end
					statMapOrderIndex = statMapOrderIndex + 1
				end
			end
			statMapOrderIndex = statMapOrderIndex + 1
			if resolveInterpolation then
				table.insert(level, statRow.BaseResolvedValues[i])
				level.statInterpolation[statMapOrderIndex] = 1
			else
				table.insert(level, statRow.FloatStatsValues[i] / math.max(statRow.InterpolationBases[i].Value, 0.00001) )
			end
		end
		if injectConstantValuesIntoEachLevel then
			for i, stat in ipairs(granted.GrantedEffectStatSets.ConstantStats) do
				if not statMap[stat.Id] then
					statMap[stat.Id] = #skill.stats + #skill.constantStats + 1
					table.insert(skill.stats, { id = stat.Id })
					if indx == 1 then
						table.insert(statMapOrder, stat.Id)
					else
						print(displayName .. ": stat missing from earlier levels: ".. stat.Id)
					end
				elseif statMapOrder[statMapOrderIndex] ~= stat.Id then
					-- add missing stats
					while statMapOrderIndex < #statMapOrder and statMapOrder[statMapOrderIndex] ~= stat.Id do
						table.insert(level, 0)
						if #level.statInterpolation < #statMapOrder then
							table.insert(level.statInterpolation, statMapOrderIndex, "0")
						end
						statMapOrderIndex = statMapOrderIndex + 1
					end
				end
				statMapOrderIndex = statMapOrderIndex + 1
				table.insert(level, granted.GrantedEffectStatSets.ConstantStatsValues[i])
				table.insert(level.statInterpolation, #statRow.FloatStats + 1, 1)
			end
		end
		for i, stat in ipairs(statRow.AdditionalStats) do
			if not statMap[stat.Id] then
				statMap[stat.Id] = #skill.stats + 1
				table.insert(skill.stats, { id = stat.Id })
				if indx == 1 then
					table.insert(statMapOrder, stat.Id)
					if stat.CannotGrantToMinion then
						table.insert(skill.CannotGrantToMinion, stat.Id)
					end
				else
					print(displayName .. ": stat missing from earlier levels: ".. stat.Id)
				end
			elseif statMapOrder[statMapOrderIndex] ~= stat.Id then
				-- add missing stats
				while statMapOrderIndex < #statMapOrder and statMapOrder[statMapOrderIndex] ~= stat.Id do
					table.insert(level, 0)
					if #level.statInterpolation < #statMapOrder then
						table.insert(level.statInterpolation, statMapOrderIndex, "0")
					end
					statMapOrderIndex = statMapOrderIndex + 1
				end
			end
			statMapOrderIndex = statMapOrderIndex + 1
			table.insert(level, statRow.AdditionalStatsValues[i])
		end
		for i, stat in ipairs(statRow.AdditionalBooleanStats) do
			if not statMap[stat.Id] then
				statMap[stat.Id] = #skill.stats + 1
				table.insert(skill.stats, { id = stat.Id })
				if stat.CannotGrantToMinion then
					table.insert(skill.CannotGrantToMinion, stat.Id)
				end
			end
		end
		table.insert(skill.levels, level)
	end
	for i, stat in ipairs(granted.GrantedEffectStatSets.ImplicitStats) do
		if not statMap[stat.Id] then
			statMap[stat.Id] = #skill.stats + 1
			table.insert(skill.stats, { id = stat.Id })
		end
	end
	for i, stat in ipairs(granted.GrantedEffectStatSets.ConstantStats) do
		table.insert(skill.constantStats, { stat.Id, granted.GrantedEffectStatSets.ConstantStatsValues[i] })
	end
	if not skill.qualityStats then
		skill.qualityStats = { }
		for i, qualityStatsRow in ipairs(dat("GrantedEffectQualityStats"):GetRowList("GrantedEffect", granted)) do
			skill.qualityStats[i] = { }
			for j, stat in ipairs(qualityStatsRow.GrantedStats) do
				table.insert(skill.qualityStats[i], { stat.Id, qualityStatsRow.StatValues[j] / 1000 })
				--ConPrintf("[%d] %s %s", i, granted.ActiveSkill.DisplayName, stat.Id)
			end
		end
	end
end

-- #flags <flag>[ <flag>[...]]
-- Sets the base flags for this active skill
directiveTable.flags = function(state, args, out)
	local skill = state.skill
	for flag in args:gmatch("%a+") do
		table.insert(skill.baseFlags, flag)
	end
end

-- #baseMod <mod definition>
-- Adds a base modifier to the skill
directiveTable.baseMod = function(state, args, out)
	local skill = state.skill
	table.insert(skill.mods, args)
end

-- #mods
-- Emits the skill modifiers
directiveTable.mods = function(state, args, out)
	local skill = state.skill
	if not args:match("noBaseFlags") then
		if not skill.isSupport then
			out:write('\tbaseFlags = {\n')
			for _, flag in ipairs(skill.baseFlags) do
				out:write('\t\t', flag, ' = true,\n')
			end
			out:write('\t},\n')
		end
	end
	if not args:match("noBaseMods") then
		if next(skill.mods) ~= nil then
			out:write('\tbaseMods = {\n')
			for _, mod in ipairs(skill.mods) do
				out:write('\t\t', mod, ',\n')
			end
			out:write('\t},\n')
		end
	end
	if not args:match("noQualityStats") then
		if next(skill.qualityStats) ~= nil then
			out:write('\tqualityStats = {\n')
			for i, alternates in ipairs(skill.qualityStats) do
				if i == 1 then
					out:write('\t\tDefault = {\n')
				else
					local value = i - 1
					out:write('\t\tAlternate' .. value .. ' = {\n')
				end
				for _, stat in ipairs(alternates) do
					out:write('\t\t\t{ "', stat[1], '", ', stat[2], ' },\n')
				end
				out:write('\t\t},\n')
			end
			out:write('\t},\n')
		end
	end
	if not args:match("noStats") then
		if next(skill.constantStats) ~= nil then
			-- write out constant stats that don't change per level
			out:write('\tconstantStats = {\n')
			for i, stat in ipairs(skill.constantStats) do
				out:write('\t\t{ "', stat[1], '", ', stat[2], ' },\n')
			end
			out:write('\t},\n')
		end
		-- write out per level stats
		out:write('\tstats = {\n')
		for _, stat in ipairs(skill.stats) do
			out:write('\t\t"', stat.id, '",\n')
		end
		out:write('\t},\n')
		if next(skill.CannotGrantToMinion) then
			out:write('\tnotMinionStat = {\n')
			for _, stat in ipairs(skill.CannotGrantToMinion) do
				out:write('\t\t"', stat, '",\n')
			end
			out:write('\t},\n')
		end
	end
	if not args:match("noLevels") then
		out:write('\tlevels = {\n')
		for index, level in ipairs(skill.levels) do
			out:write('\t\t[', level.level, '] = { ')
			for _, statVal in ipairs(level) do
				out:write(tostring(statVal), ', ')
			end
			for k, v in pairs(level.extra) do
				out:write(k, ' = ', tostring(v), ', ')
			end
			if next(level.statInterpolation) ~= nil then
				out:write('statInterpolation = { ')
				for _, type in ipairs(level.statInterpolation) do
					out:write(type, ', ')
				end
				out:write('}, ')
			end
			if next(level.cost) ~= nil then
				out:write('cost = { ')
				for k, v in pairs(level.cost) do
					out:write(k, ' = ', tostring(v), ', ')
				end
				out:write('}, ')
			end
			out:write('},\n')
		end
		out:write('\t},\n')
	end
	out:write('}')
	state.skill = nil
end

for _, name in pairs({"act_str","act_dex","act_int","other","glove","minion","spectre","sup_str","sup_dex","sup_int"}) do
	processTemplateFile(name, "Skills/", "../Data/Skills/", directiveTable)
end

local out = io.open("../Data/Gems.lua", "w")
out:write('-- This file is automatically generated, do not edit!\n')
out:write('-- Gem data (c) Grinding Gear Games\n\nreturn {\n')
for skillGem in dat("SkillGems"):Rows() do
	for _, gemEffect in ipairs(skillGem.GemVariants) do
		if gems[gemEffect.Id] then
			out:write('\t["', "Metadata/Items/Gems/SkillGem" .. gemEffect.Id, '"] = {\n')
			out:write('\t\tname = "', fullNameGems[skillGem.BaseItemType.Id] and skillGem.BaseItemType.Name or trueGemNames[gemEffect.Id] or skillGem.BaseItemType.Name:gsub(" Support",""), '",\n')
			-- Hybrid gems (e.g. Vaal gems) use the display name of the active skill e.g. Vaal Summon Skeletons of Sorcery
			if not skillGem.IsSupport then
				out:write('\t\tbaseTypeName = "', gemEffect.GrantedEffect.ActiveSkill.DisplayName, '",\n')
			end
			out:write('\t\tgameId = "', skillGem.BaseItemType.Id, '",\n')
			out:write('\t\tvariantId = "', gemEffect.Id, '",\n')
			out:write('\t\tgrantedEffectId = "', gemEffect.GrantedEffect.Id, '",\n')
			if gemEffect.GrantedEffect2 then
				out:write('\t\tsecondaryGrantedEffectId = "', gemEffect.GrantedEffect2.Id, '",\n')
			end
			if #gemEffect.SecondarySupportName > 0 then
				out:write('\t\tsecondaryEffectName = "', gemEffect.SecondarySupportName, '",\n')
			end
			if skillGem.IsVaalGem then
				out:write('\t\tvaalGem = true,\n')
			end
			local tagNames = { }
			out:write('\t\ttags = {\n')
			for _, tag in ipairs(gemEffect.Tags) do
				out:write('\t\t\t', tag.Id, ' = true,\n')
				if #tag.Name > 0 then
					table.insert(tagNames, tag.Name)
				end
			end
			out:write('\t\t},\n')
			out:write('\t\ttagString = "', table.concat(tagNames, ", "), '",\n')
			out:write('\t\treqStr = ', skillGem.Str, ',\n')
			out:write('\t\treqDex = ', skillGem.Dex, ',\n')
			out:write('\t\treqInt = ', skillGem.Int, ',\n')
			local naturalMaxLevel = #dat("ItemExperiencePerLevel"):GetRowList("ItemExperienceType", skillGem.GemLevelProgression)
			out:write('\t\tnaturalMaxLevel = ', naturalMaxLevel > 0 and naturalMaxLevel or 1, ',\n')
			out:write('\t},\n')
		end
	end
end
out:write('}')
out:close()

print("Skill data exported.")
