local HIDE_INACTIVE_NODE = false;

local _, addon = ...

local LoadingBarUtil = addon.TalentTreeLoadingBarUtil;
local ClassTalentTooltipUtil = addon.ClassTalentTooltipUtil;
local DataProvider = addon.TalentTreeDataProvider;
local OnEnterDelay = addon.TalentTreeOnEnterDelay;

local After = C_Timer.After;
local C_Traits = C_Traits;
local C_ClassTalents = C_ClassTalents;
local C_SpecializationInfo = C_SpecializationInfo;
local GetNodeInfo = C_Traits and C_Traits.GetNodeInfo;
local GetEntryInfo = C_Traits and C_Traits.GetEntryInfo;
local C_PvP = C_PvP;

local GetSpecializationInfoByID = GetSpecializationInfoByID;
local GetInspectSpecialization = GetInspectSpecialization;
local UnitClass = UnitClass;
local UnitSex = UnitSex;
--local INSPECT_TRAIT_CONFIG_ID = -1;

local sqrt = math.sqrt;
local atan2 = math.atan2;
local ipairs = ipairs;
local floor = math.floor;

local BUTTON_PIXEL_SIZE = 32;
local ICON_PIXEL_SIZE = 24;
local DISTANCE_UNIT = 300;  --600    --neighboring node distance 600
local PADDING = 1;
local HEADER_SIZE;
local SECTOR_WIDTH = 11;    --the width of each tab (spec talent, class talent). unit is button wdith.

local BUTTON_SIZE = 32;
local BUTTON_SIZE_HALF = BUTTON_SIZE * 0.5;
local BRANCH_WEIGHT = 1;
local DISTANCE_RATIO = 0.05;
local ICON_SIZE = 28;
local FONT_HEIGHT = 16;
local PIXEL = 1;

local MainFrame;
local Nodes = {};
local Branches = {};

local EntryInfoCache = {};
local NodeInfoCache = {};
local NodeIDxNode = {};



function DataProvider:WipeNodeCache()
    EntryInfoCache = {};
    NodeInfoCache = {};
end


local LoadoutUtil = {};
local LayoutUtil = {};

function LayoutUtil:Reset()
    self.leftMinX = 69;
    self.leftMaxX = 0;
    self.leftMinY = 69;
    self.rightMinX = 69;
    self.rightMaxX = 1;
    self.rightMinY = 69;
end

function LayoutUtil:UpdateFrameSize()
    local pvpFrameWidth = (MainFrame.PvPTalentFrame:IsShown() and 3*BUTTON_SIZE) or 0;
    MainFrame:SetSize(BUTTON_SIZE * 2 * SECTOR_WIDTH + pvpFrameWidth, BUTTON_SIZE * (10 + 2*PADDING) + HEADER_SIZE);
end

function LayoutUtil:UpdateNodePosition()
    local tileSize = BUTTON_SIZE;
    local container = MainFrame;

    local leftMinY = self.leftMinY;
    local leftMinX = self.leftMinX;
    local leftMaxX = self.leftMaxX;
    local leftSpanX = leftMaxX - leftMinX + 1;

    local rightMinY = self.rightMinY;
    local rightMinX = self.rightMinX;
    local rightMaxX = self.rightMaxX;
    local rightSpanX = rightMaxX - rightMinX + 1;

    local leftFromOffsetX = (SECTOR_WIDTH - leftSpanX)*0.5*tileSize;
    local rightFromOffsetX = ((SECTOR_WIDTH - rightSpanX)*0.5 + SECTOR_WIDTH) *tileSize;

    local fromOffsetY = - HEADER_SIZE - tileSize;
    local node, x, y;

    for i = 1, MainFrame.numAcitveNodes do
        node = Nodes[i];
        node:ClearAllPoints();
        if node.isLeft then
            x = (node.iX - leftMinX) * tileSize + leftFromOffsetX;
            y = -(node.iY - leftMinY) * tileSize + fromOffsetY;
            --node.Order:SetText(node.iX - leftMinX)
        else
            x = (node.iX - rightMinX) * tileSize + rightFromOffsetX;
            y = -(node.iY - rightMinY) * tileSize + fromOffsetY;
            node:SetPoint("TOPLEFT", container, "TOPLEFT", x, y);
            --node.Order:SetText(node.iX - rightMinX)
        end
        node:SetPoint("TOPLEFT", container, "TOPLEFT", x, y);
        node.x, node.y = x, y;
    end
end

function LayoutUtil:SetNodeTileIndex(node, isLeft, iX, iY)
    node.iX = iX;
    node.iY = iY;
    node.isLeft = isLeft or nil;

    if isLeft then
        if iX > self.leftMaxX then
            self.leftMaxX = iX;
        end
        if iX < self.leftMinX then
            self.leftMinX = iX;
        end
        if iY < self.leftMinY then
            self.leftMinY = iY;
        end
    else
        if iX > self.rightMaxX then
            self.rightMaxX = iX;
        end
        if iX < self.rightMinX then
            self.rightMinX = iX;
        end
        if iY < self.rightMinY then
            self.rightMinY = iY;
        end
    end
end


local function CalculateNormalizedPosition(posX, posY)
    posX = posX - 1800;
    posY = 1200 - posY;
    
    posX = floor(posX/DISTANCE_UNIT + 0.5);
    posY = floor(posY/DISTANCE_UNIT + 0.5);

    posY = -posY;

    posX = posX * 0.5;
    posY = posY * 0.5;

    local isLeftSide;

    if posX >= 12.5 then
        posX = posX - 2
    else
        isLeftSide = true
    end

    return posX, posY, isLeftSide
end


local function SetBranchColorYellow(branch, isActive)
    if isActive then
        branch:SetVertexColor(0.72, 0.6, 0);
    else
        branch:SetVertexColor(0.200, 0.200, 0.200);
    end
end

local function SetBranchColorCyan(branch, isActive)
    if isActive then
        branch:SetVertexColor(0, 0.53, 0.65);
    else
        branch:SetVertexColor(0.200, 0.200, 0.200);
    end
end

local SetBranchColor = SetBranchColorYellow;







NarciMiniTalentTreeMixin = {};

function NarciMiniTalentTreeMixin:OnLoad()
    MainFrame = self;

    local px = NarciAPI.GetPixelForWidget(self, 1);
    PIXEL = px;
    BRANCH_WEIGHT = NarciAPI.GetPixelForWidget(self, 2);
    BUTTON_SIZE = NarciAPI.GetPixelForWidget(self, BUTTON_PIXEL_SIZE);
    ICON_SIZE = NarciAPI.GetPixelForWidget(self, ICON_PIXEL_SIZE);
    DISTANCE_RATIO = BUTTON_SIZE / 600;
    BUTTON_SIZE_HALF = BUTTON_SIZE * 0.5;
    FONT_HEIGHT = 16 * px;
    HEADER_SIZE = FONT_HEIGHT + BUTTON_SIZE;

    LayoutUtil.fromOffsetX = -3 * 600*DISTANCE_RATIO;
    LayoutUtil.fromOffsetY = 2 * 600*DISTANCE_RATIO;

    LayoutUtil:UpdateFrameSize();

    self.ClassName:ClearAllPoints();
    self.ClassName:SetPoint("TOP", self, "TOPLEFT", (4.5 + PADDING) * BUTTON_SIZE, -PADDING*BUTTON_SIZE);

    self.SpecName:ClearAllPoints();
    self.SpecName:SetPoint("TOP", self, "TOPLEFT", (15.5 + PADDING) * BUTTON_SIZE, -PADDING*BUTTON_SIZE);

    local font, height, flag = self.LoadoutToggle.ButtonText:GetFont();
    self.ClassName:SetFont(font, BUTTON_SIZE, flag);
    self.SpecName:SetFont(font, BUTTON_SIZE, flag);


    local hitrectCompensation = (16*px - 16)/2;
    if hitrectCompensation > 0 then
        hitrectCompensation = 0;
    end


    local function LoadoutToggle_OnEnter(f)
        f.ButtonText:SetTextColor(0.92, 0.92, 0.92);
        f.Arrow:SetVertexColor(0.92, 0.92, 0.92);
    end

    local function LoadoutToggle_OnLeave(f)
        f.ButtonText:SetTextColor(0.67, 0.67, 0.67);
        f.Arrow:SetVertexColor(0.67, 0.67, 0.67);
    end

    self.LoadoutToggle.ButtonText:SetFont(font, FONT_HEIGHT, "");
    self.LoadoutToggle:ClearAllPoints();
    self.LoadoutToggle:SetPoint("TOP", self, "TOPLEFT", SECTOR_WIDTH * BUTTON_SIZE, -BUTTON_SIZE);
    self.LoadoutToggle:SetHeight(FONT_HEIGHT);
    self.LoadoutToggle:SetHitRectInsets(0, 0, hitrectCompensation, hitrectCompensation);
    self.LoadoutToggle.Arrow:SetSize(FONT_HEIGHT, FONT_HEIGHT);
    self.LoadoutToggle:SetScript("OnClick", function()
        LoadoutUtil:ToggleList();
    end);
    self.LoadoutToggle:SetScript("OnEnter", LoadoutToggle_OnEnter);
    self.LoadoutToggle:SetScript("OnLeave", LoadoutToggle_OnLeave);

    self.HeaderLight:ClearAllPoints();
    self.HeaderLight:SetPoint("TOP", self, "TOPLEFT", SECTOR_WIDTH * BUTTON_SIZE, 0);
    self.HeaderLight:SetSize(SECTOR_WIDTH * BUTTON_SIZE * 2 , SECTOR_WIDTH * BUTTON_SIZE * 0.5, 0);

    self.SpecTabToggle:SetHeight(FONT_HEIGHT);
    self.SpecTabToggle:SetHitRectInsets(-4, 0, hitrectCompensation, hitrectCompensation);
    self.SpecTabToggle:SetPoint("TOPLEFT", self, "TOPLEFT", BUTTON_SIZE, -BUTTON_SIZE);
    self.SpecTabToggle.ButtonText:SetFont(font, FONT_HEIGHT, "");
    self.SpecTabToggle.ButtonText:SetPoint("LEFT", 12*px, 0);
    self.SpecTabToggle.Arrow:SetSize(FONT_HEIGHT, FONT_HEIGHT);
    self.SpecTabToggle.Arrow:SetPoint("LEFT", 0, -px);


    local function SpecTabToggle_OnEnter(f)
        f.ButtonText:SetTextColor(0.8, 0.8, 0.8);
        f.Arrow:SetVertexColor(0.8, 0.8, 0.8);
    end

    local function SpecTabToggle_OnLeave(f)
        f.ButtonText:SetTextColor(0.5, 0.5, 0.5);
        f.Arrow:SetVertexColor(0.5, 0.5, 0.5);
    end

    local function SpecTabToggle_OnClick(f)
        MainFrame.SpecSelect:ShowFrame();
    end
   
    self.SpecTabToggle:SetScript("OnEnter", SpecTabToggle_OnEnter);
    self.SpecTabToggle:SetScript("OnLeave", SpecTabToggle_OnLeave);
    self.SpecTabToggle:SetScript("OnClick", SpecTabToggle_OnClick);

    self.PvPTalentToggle:SetHeight(FONT_HEIGHT);
    self.PvPTalentToggle:SetHitRectInsets(0, -8, hitrectCompensation, hitrectCompensation);
    self.PvPTalentToggle:SetPoint("TOPRIGHT", self, "TOPRIGHT", -BUTTON_SIZE, -BUTTON_SIZE);
    self.PvPTalentToggle.ButtonText:SetFont(font, FONT_HEIGHT, "");
    self.PvPTalentToggle.ButtonText:SetPoint("RIGHT", -12*px, 0);
    self.PvPTalentToggle.Arrow:SetSize(FONT_HEIGHT/2, FONT_HEIGHT);
    self.PvPTalentToggle.Arrow:SetPoint("RIGHT", 0, -px);
    self.PvPTalentToggle:SetScript("OnEnter", SpecTabToggle_OnEnter);
    self.PvPTalentToggle:SetScript("OnLeave", SpecTabToggle_OnLeave);
    self.PvPTalentToggle:SetScript("OnClick", function(f)
        MainFrame.PvPTalentFrame:Toggle();
    end);

    self.Divider:ClearAllPoints();
    self.Divider:SetPoint("TOPLEFT", self, "TOPLEFT", SECTOR_WIDTH * BUTTON_SIZE, -BUTTON_SIZE/2 -HEADER_SIZE);
    self.Divider:SetPoint("BOTTOMLEFT", self, "BOTTOM", 0, BUTTON_SIZE/2);
end

function NarciMiniTalentTreeMixin:Init()
    if self.PvPTalentFrame:IsWarModeActive() then
        self.PvPTalentFrame:Toggle();
    end
    self.Init = nil;
end

function NarciMiniTalentTreeMixin:ShowActiveBuild()
    local configID = C_ClassTalents.GetActiveConfigID();
    self:ShowConfig(configID);
end

function NarciMiniTalentTreeMixin:ShowConfig(configID, isPreviewing)
    if configID ~= self.configID then
        DataProvider:WipeNodeCache();
    else
        return
    end

    self:ReleaseAllNodes();

    local specID = DataProvider:GetCurrentSpecID();
    local configInfo = C_Traits.GetConfigInfo(configID);
    local treeID = configInfo.treeIDs[1]
	local nodeIDs = C_Traits.GetTreeNodes(treeID);

    self.configID = configID;
    self.treeID = treeID;
    self.nodeIDs = nodeIDs;

    local isInspecting = self:IsInspecting();

    for i, nodeID in ipairs(nodeIDs) do
		self:InstantiateTalentButton(nodeID, nil, isInspecting);
	end

    LayoutUtil:UpdateNodePosition();

    --After(0, function()
        self:CreateBranches()
    --end)

    if not isPreviewing and not isInspecting then
        self.LoadoutToggle.ButtonText:SetText(DataProvider:GetActiveLoadoutName());
        self.SpecSelect:SetSelectedSpec(specID);
        LoadoutUtil:SetActiveConfigID(DataProvider:GetSelecetdConfigID());
        LoadingBarUtil:HideBar();
    end
end

function NarciMiniTalentTreeMixin:ShowInspecting(inspectUnit)
    self.inspectUnit = inspectUnit;
    self:SetInspectionMode(true);
    self:ShowConfig(-1);
end


function NarciMiniTalentTreeMixin:SetInspectionMode(state)
    if state then
        self.SpecTabToggle:Hide();
        self.LoadoutToggle:Disable();
        self.LoadoutToggle.Arrow:Hide();
        --self.LoadoutToggle.ButtonText:SetText(TALENTS_INSPECT_FORMAT:format(UnitName(self.inspectUnit)));
        LoadingBarUtil:HideBar();
        if self.SpecSelect:IsShown() then
            self.SpecSelect:CloseFrame(true);
        end
        
        if self.PvPTalentFrame:IsShown() then
            self.PvPTalentFrame:Update();
        end

        local loadoutName;
        local unit = self:GetInspectUnit();
        local playerName = UnitName(unit);
        local specID = GetInspectSpecialization(unit);
		local classDisplayName, class = UnitClass(unit);
		if specID then
            local sex = UnitSex(unit);
			local _, specName = GetSpecializationInfoByID(specID, sex);
            loadoutName = specName.." "..classDisplayName.." - "..playerName;
        else
            loadoutName = TALENTS_INSPECT_FORMAT:format(playerName);
		end
        self.LoadoutToggle.ButtonText:SetText(loadoutName);
        SetBranchColor = SetBranchColorCyan;

    elseif self:IsInspecting() then
        self.inspectUnit = nil;
        self.SpecTabToggle:Show();
        self.LoadoutToggle:Enable();
        self.LoadoutToggle.Arrow:Show();
        self.LoadoutToggle.ButtonText:SetText(DataProvider:GetActiveLoadoutName());
        if self.PvPTalentFrame:IsShown() then
            self.PvPTalentFrame:Update();
        end
        SetBranchColor = SetBranchColorYellow;
    end
end

local PRINTED_INDEX = {
    [118] = true,
    [119] = true,
    [103] = true,
    [112] = true,
    [102] = true,
}

local function SetNodePosition(node, relativeTo, x, y)
    y = y - HEADER_SIZE;
    --node:SetPoint("TOPLEFT", relativeTo, "TOPLEFT", x, y);
    node.x = x;
    node.y = y;
end

function NarciMiniTalentTreeMixin:InstantiateTalentButton(nodeID, nodeInfo, isInspecting)
    nodeInfo = nodeInfo or self:GetAndCacheNodeInfo(nodeID);

    if not nodeInfo.isVisible then
		return nil;
	end

    local activeEntryID = nodeInfo.activeEntry and nodeInfo.activeEntry.entryID or nil;
	local entryInfo = (activeEntryID ~= nil) and self:GetAndCacheEntryInfo(activeEntryID) or nil;
	local talentType = (entryInfo ~= nil) and entryInfo.type or nil;

    if nodeInfo.posY < 0 then
        --Button #1 is out-of-bound for some reason
        return
    end

    local node = self:AcquireNode();
    node:SetBorderColor(isInspecting);

    local iX, iY, isLeftSide = CalculateNormalizedPosition(nodeInfo.posX, nodeInfo.posY);

    if (nodeInfo.ranksPurchased > 0) or (nodeInfo.activeRank > 0) or (DataProvider:IsAutoGrantedTalent(nodeID)) then
        --ranksPurchased is 0 for freely-granted talent (some talent in the first row)
        node.active = true;
        if nodeInfo.ranksPurchased == 0 then
            node.currentRank = 1;
        else
            node.currentRank = nodeInfo.ranksPurchased;
        end
    else
        if HIDE_INACTIVE_NODE then
            node:Hide();
        end
        node.active = nil;
        node.currentRank = 0;
    end
    node.maxRanks = nodeInfo.maxRanks;

    LayoutUtil:SetNodeTileIndex(node, isLeftSide, iX, iY);

    NodeIDxNode[nodeID] = node;

    if nodeInfo.type == 2 then
        node.entryIDs = nodeInfo.entryIDs;
        if activeEntryID == nodeInfo.entryIDs[1] then
            node:SetNodeType(2, 1);
        elseif activeEntryID == nodeInfo.entryIDs[2] then
            node:SetNodeType(2, 2);
        else
            node:SetNodeType(2, 0);
        end
        --[[
        if nodeInfo and (nodeInfo.type == Enum.TraitNodeType.Selection) then
			if FlagsUtil.IsSet(nodeInfo.flags, Enum.TraitNodeFlag.ShowMultipleIcons) then
				return "ClassTalentButtonChoiceTemplate";
			end
		end
        --]]
    else
        node.entryIDs = nil;
        if talentType == 0 then
            --*Warrior Why do some passive traits use this type?
            node:SetNodeType(1, 0);
        elseif talentType == 1 then --square
            node:SetNodeType(0);
        elseif talentType == 2 then --circle
            if nodeInfo.type == 0 then
                if nodeInfo.maxRanks == 1 then  --1/1
                    node:SetNodeType(1, 0);
                elseif nodeInfo.maxRanks == 2 then
                    if nodeInfo.ranksPurchased == 1 then    --1/2
                        node:SetNodeType(1, 1);
                    else    --2/2
                        node:SetNodeType(1, 0);
                    end
                elseif nodeInfo.maxRanks == 3 then
                    if nodeInfo.ranksPurchased == 1 then    --1/3
                        node:SetNodeType(1, 1);
                    elseif nodeInfo.ranksPurchased == 2 then    --2/3
                        node:SetNodeType(1, 2);
                    else    --3/3
                        node:SetNodeType(1, 0);
                    end
                else
                    --0/4?
                    node.Symbol:SetVertexColor(1, 0, 0);
                    node:SetNodeType(1, 0);
                end
            else
                node:Hide();
            end
        else
            --nil is unselected octagon
            node.Symbol:SetVertexColor(1, 0, 0);
            print(talentType, "Unknown type")
        end
    end


    if node.active then
        node.Symbol:SetVertexColor(0.67, 0.67, 0.67);
        node:SetActive(true);
    else
        node.Symbol:SetVertexColor(0.160, 0.160, 0.160);
        node:SetActive(false);
    end

    if entryInfo then
        node:SetDefinitionID(entryInfo.definitionID);
    end

    node.nodeID = nodeID;
    node.entryID = activeEntryID;
    node.rank = nodeInfo.ranksPurchased;
end

function NarciMiniTalentTreeMixin:ReleaseAllNodes()
    for i = 1, #Nodes do
        Nodes[i]:Hide();
        Nodes[i]:ClearAllPoints();
    end
    self.numAcitveNodes = 0;

    LayoutUtil:Reset();
end

function NarciMiniTalentTreeMixin:AcquireNode()
    self.numAcitveNodes = self.numAcitveNodes + 1;
    if not Nodes[self.numAcitveNodes] then
        Nodes[self.numAcitveNodes] = CreateFrame("Frame", nil, self, "NarciTalentTreeNodeTemplate");
        Nodes[self.numAcitveNodes].Symbol:SetVertexColor(0.67, 0.67, 0.67);
        Nodes[self.numAcitveNodes].Icon:SetSize(ICON_SIZE, ICON_SIZE);
        Nodes[self.numAcitveNodes]:SetSize(BUTTON_SIZE, BUTTON_SIZE);
    end
    Nodes[self.numAcitveNodes]:Show();
    return Nodes[self.numAcitveNodes];
end

function NarciMiniTalentTreeMixin:GetAndCacheEntryInfo(entryID)
    if not EntryInfoCache[entryID] then
        EntryInfoCache[entryID] = GetEntryInfo(self.configID, entryID);
    end
    return EntryInfoCache[entryID];
end

function NarciMiniTalentTreeMixin:GetAndCacheNodeInfo(nodeID)
    if not NodeInfoCache[nodeID] then
        NodeInfoCache[nodeID] = GetNodeInfo(self.configID, nodeID);
    end
    return NodeInfoCache[nodeID];
end


local BranchUpdater = CreateFrame("Frame");

local MAX_PROCESS_PER_FRAME = 400;    --20

local function BranchUpdater_OnUpdate(self, elapsed)
    local processedThisFrame = 0;

    local nodeID;
    local nodeInfo;
    local fromNode, targetNode;
    local b;
    local x1, y1, x2, y2, d, rd;
    local bchs = Branches;
    local SZH = BUTTON_SIZE_HALF;
    local notFound = true;
    local main = MainFrame;

    while self.fromNodeIndex <= self.numNodes do
        if self.lastEdgeID and self.lastNodeInfo then
            fromNode = self.lastFromNode;
            for j = self.lastEdgeID, #self.lastNodeInfo.visibleEdges do
                targetNode = NodeIDxNode[self.lastNodeInfo.visibleEdges[j].targetNode];
                if targetNode then
                    processedThisFrame = processedThisFrame + 1;
                    if processedThisFrame > MAX_PROCESS_PER_FRAME then
                        self.lastEdgeID = j;
                        self.lastNodeInfo = nodeInfo;
                        return
                    end
                    self.numBranches = self.numBranches + 1;
                    b = bchs[self.numBranches];
                    if not b then
                        b = MainFrame:CreateTexture(nil, "OVERLAY");
                        bchs[self.numBranches] = b;
                        b:SetHeight(BRANCH_WEIGHT);
                        b:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\TalentTree\\Branch");
                    end
                    x1, y1 = fromNode.x + SZH, fromNode.y - SZH;
                    x2, y2 = targetNode.x + SZH, targetNode.y - SZH;
                    d = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
                    rd = atan2(y2 - y1, x2 - x1);
                    b:SetWidth(d);
                    b:ClearAllPoints();
                    if fromNode.isLeft then
                        b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5);
                    else
                        b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5);
                    end
                    
                    b:SetRotation(rd);
                    b:Show();

                    SetBranchColor(b, fromNode.active and targetNode.active);
                end
            end

            self.fromNodeIndex = self.fromNodeIndex + 1;
            self.lastEdgeID = nil;
            self.lastNodeInfo = nil;
            self.lastFromNode = nil;

            if self.fromNodeIndex > self.numNodes then
                break
            end
        end

        nodeID = MainFrame.nodeIDs[self.fromNodeIndex];
        nodeInfo = MainFrame:GetAndCacheNodeInfo(nodeID);

        if nodeInfo then
            fromNode = NodeIDxNode[nodeID];
            if fromNode then
                --print(nodeID)
                --notFound = false;
                for j, edgeVisualInfo in ipairs(nodeInfo.visibleEdges) do
                    targetNode = NodeIDxNode[edgeVisualInfo.targetNode];
                    if targetNode then
                        notFound = false;
                        processedThisFrame = processedThisFrame + 1;
                        if processedThisFrame > MAX_PROCESS_PER_FRAME then
                            self.lastEdgeID = j;
                            self.lastNodeInfo = nodeInfo;
                            self.lastFromNode = fromNode;
                            return
                        end
                        self.numBranches = self.numBranches + 1;
                        b = bchs[self.numBranches];
                        if not b then
                            b = MainFrame:CreateTexture(nil, "OVERLAY");
                            bchs[self.numBranches] = b;
                            b:SetHeight(BRANCH_WEIGHT);
                            b:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\TalentTree\\Branch");
                        end
                        x1, y1 = fromNode.x + SZH, fromNode.y - SZH;
                        x2, y2 = targetNode.x + SZH, targetNode.y - SZH;
                        d = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
                        rd = atan2(y2 - y1, x2 - x1);
                        b:SetWidth(d);
                        b:ClearAllPoints();
                        if fromNode.isLeft then
                            b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5);
                        else
                            b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5);
                        end
                        b:SetRotation(rd);
                        b:Show();

                        SetBranchColor(b, fromNode.active and targetNode.active);
                    end
                end
            end
        end

        self.fromNodeIndex = self.fromNodeIndex + 1;
    end

    if notFound then
        self:SetScript("OnUpdate", nil);
        --print("Branches: "..self.numBranches);
    end
end

local function BranchUpdater_OnUpdate_OneFrame(self, elapsed)
    for i = 1, #Branches do
        Branches[i]:Hide();
        Branches[i]:ClearAllPoints();
    end

    local nodeID;
    local nodeInfo;
    local fromNode, targetNode;
    local b;
    local x1, y1, x2, y2, d, rd;
    local bchs = Branches;
    local SZH = BUTTON_SIZE_HALF;
    local main = MainFrame;

    while self.fromNodeIndex <= self.numNodes do
        nodeID = MainFrame.nodeIDs[self.fromNodeIndex];
        nodeInfo = MainFrame:GetAndCacheNodeInfo(nodeID);

        if nodeInfo then
            fromNode = NodeIDxNode[nodeID];
            if fromNode then
                for j, edgeVisualInfo in ipairs(nodeInfo.visibleEdges) do
                    targetNode = NodeIDxNode[edgeVisualInfo.targetNode];
                    if targetNode then
                        self.numBranches = self.numBranches + 1;
                        b = bchs[self.numBranches];
                        if not b then
                            b = MainFrame:CreateTexture(nil, "OVERLAY");
                            bchs[self.numBranches] = b;
                            b:SetHeight(BRANCH_WEIGHT);
                            b:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\TalentTree\\Branch");
                        end
                        x1, y1 = fromNode.x + SZH, fromNode.y - SZH;
                        x2, y2 = targetNode.x + SZH, targetNode.y - SZH;
                        d = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
                        rd = atan2(y2 - y1, x2 - x1);
                        b:SetWidth(d);
                        b:ClearAllPoints();
                        if fromNode.isLeft then
                            b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5);
                        else
                            b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5);
                        end
                        b:SetRotation(rd);
                        b:Show();

                        SetBranchColor(b, fromNode.active and targetNode.active);
                    end
                end
            end
        end

        self.fromNodeIndex = self.fromNodeIndex + 1;
    end

    self:SetScript("OnUpdate", nil);
end

function BranchUpdater:StartUpdating()
    self.numBranches = 0;
    self.fromNodeIndex = 1;
    self.numNodes = #MainFrame.nodeIDs;
    self:SetScript("OnUpdate", BranchUpdater_OnUpdate_OneFrame);
end

function NarciMiniTalentTreeMixin:CreateBranches()
    if true then
        BranchUpdater:StartUpdating();
        return
    end

	local nodeIDs = self.nodeIDs or C_Traits.GetTreeNodes(self.treeID);
    local configID = self.configID;
    local nodeInfo;

    local fromNode, targetNode;

    local total = 0;
    local bchs = Branches;
    local x1, y1, x2, y2, d, rd;
    local b;
    local sqrt = sqrt;
    local atan2 = atan2;
    local SZH = BUTTON_SIZE_HALF;

    for i, nodeID in ipairs(nodeIDs) do
		nodeInfo = self:GetAndCacheNodeInfo(nodeID);
        if nodeInfo then
            fromNode = NodeIDxNode[nodeID];
            if fromNode then
                for j, edgeVisualInfo in ipairs(nodeInfo.visibleEdges) do
                    targetNode = NodeIDxNode[edgeVisualInfo.targetNode];
                    if targetNode then
                        --self:LinkNode(fromNode, targetNode);
                        total = total + 1;
                        b = bchs[total];
                        if not b then
                            b = self:CreateTexture(nil, "OVERLAY");
                            bchs[total] = b;
                            b:SetHeight(BRANCH_WEIGHT);
                            b:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\TalentTree\\Branch")
                        end
                        x1, y1 = fromNode.x + SZH, fromNode.y - SZH;
                        x2, y2 = targetNode.x + SZH, targetNode.y - SZH;
                        d = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
                        rd = atan2(y2 - y1, x2 - x1);
                        b:SetWidth(d);
                        b:SetPoint("CENTER", self, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5);
                        b:SetRotation(rd);
                        b:Show();

                        if fromNode.active and targetNode.active then
                            b:SetVertexColor(0.4, 0.4, 0.4);
                        else
                            b:SetVertexColor(0.160, 0.160, 0.160);
                        end
                    end
                end
            end
		end
	end
end

local Events = {
    "TRAIT_TREE_CHANGED", "TRAIT_NODE_CHANGED", "TRAIT_NODE_CHANGED_PARTIAL", "TRAIT_NODE_ENTRY_UPDATED", "TRAIT_CONFIG_UPDATED", "ACTIVE_PLAYER_SPECIALIZATION_CHANGED",

    --TRAIT_NODE_CHANGED: Fires multiple times when cancel switching talent
    --TRAIT_TREE_CHANGED: After clicking a loadout
    --TRAIT_CONFIG_UPDATED: After successfully changing loadout
    --ACTIVE_PLAYER_SPECIALIZATION_CHANGED: followed by TRAIT_CONFIG_UPDATED
}

function NarciMiniTalentTreeMixin:OnShow()
    for i, event in ipairs(Events) do
        self:RegisterEvent(event);
    end
    
    if self.Init then
        self:Init();
    end
end


function NarciMiniTalentTreeMixin:OnEvent(event, ...)
    if event == "TRAIT_TREE_CHANGED" then
        
    elseif event == "TRAIT_NODE_CHANGED" then

    elseif event == "TRAIT_NODE_CHANGED_PARTIAL" then

    elseif event == "TRAIT_NODE_ENTRY_UPDATED" then

    elseif event == "TRAIT_CONFIG_UPDATED" then
        After(0, function()
            self:ShowActiveBuild();

            --if ClassTalentFrame then
            --    ClassTalentFrame:OnEvent("ACTIVE_PLAYER_SPECIALIZATION_CHANGED");
            --end
        end)
    elseif event == "ACTIVE_PLAYER_SPECIALIZATION_CHANGED" then
        if self.SpecSelect:IsShown() then
            self.SpecSelect:CloseFrame();
        end
        DataProvider:UpdateSpecInfo();
        self:ShowActiveBuild();
    end
end

function NarciMiniTalentTreeMixin:IsInspecting()
    return self.inspectUnit ~= nil
end

function NarciMiniTalentTreeMixin:GetInspectUnit()
    return self.inspectUnit;
end

function NarciMiniTalentTreeMixin:AnchorToInspectFrame()
    self.anchor = "inspectframe";
    self:ClearAllPoints();
    self:SetPoint("TOPLEFT", InspectFrame, "TOPRIGHT", 4, 0);
end

function NarciMiniTalentTreeMixin:AnchorToPaperDoll()
    self.anchor = "paperdoll";
    self:ClearAllPoints();
    self:SetPoint("TOPLEFT", PaperDollFrame, "TOPRIGHT", 4, 0);
end


--[[
    
function ClassTalentTalentsTabMixin:GetSpecID()
	if self:IsInspecting() then
		return GetInspectSpecialization(self:GetInspectUnit());
	end

	return PlayerUtil.GetCurrentSpecID();
end

]]

NarciTalentTreeLoadoutButtonMixin = {};

function NarciTalentTreeLoadoutButtonMixin:OnEnter()
    if not self.selected then
        self.ButtonText:SetTextColor(0.92, 0.92, 0.92);
    end

    if self.configID then
        OnEnterDelay:WatchButton(self);
    end
end

function NarciTalentTreeLoadoutButtonMixin:OnEnterCallback()
    if self.configID then
        MainFrame:ShowConfig(self.configID, true);
    end
end

function NarciTalentTreeLoadoutButtonMixin:OnLeave()
    if not self.selected then
        self.ButtonText:SetTextColor(0.5, 0.5, 0.5);
    end
    OnEnterDelay:ClearButton();
end

function NarciTalentTreeLoadoutButtonMixin:OnClick()
    if self.selected then

    else
        if self.configID then
            LoadingBarUtil:SetFromLoadoutToggle(MainFrame.LoadoutToggle);

            local autoApply = true;
            local result = C_ClassTalents.LoadConfig(self.configID, autoApply);
            if result ~= 0 then
                local currentSpecID = DataProvider:GetCurrentSpecID();
                if currentSpecID then
                    C_ClassTalents.UpdateLastSelectedSavedConfigID(currentSpecID, self.configID);
                end
            end
        end
    end
    LoadoutUtil:HideList();
end

function NarciTalentTreeLoadoutButtonMixin:SetConfigID(configID)
    self.configID = configID;

    local info = C_Traits.GetConfigInfo(configID);
    if info and info.name then
        self.ButtonText:SetText(info.name);
        self.Underline:SetWidth(self.ButtonText:GetWrappedWidth());
    else

    end
end

function LoadoutUtil:IsFocused()
    if MainFrame.LoadoutToggle:IsMouseOver(12, -12, 0, 0) then
        return true
    end

    for i = 1, #self.buttons do
        if self.buttons[i]:IsShown() then
            if self.buttons[i]:IsMouseOver() then
                return true
            end
        else
            return
        end
    end
end

function LoadoutUtil:Init()
    self.buttons = {};

    local function LoadoutDropdown_OnShow(f)
        f:RegisterEvent("GLOBAL_MOUSE_DOWN");
    end

    local function LoadoutDropdown_OnEvent(f, event, ...)
        if not self:IsFocused() then
            f:UnregisterEvent("GLOBAL_MOUSE_DOWN");
            self:HideList();
        end
    end

    self.container = MainFrame.LoadoutDropdown;

    self.container:SetScript("OnShow", LoadoutDropdown_OnShow);
    self.container:SetScript("OnEvent", LoadoutDropdown_OnEvent);

    self.font = MainFrame.LoadoutToggle.ButtonText:GetFont();
    self.buttonHeight = 32*PIXEL;

    self.Init = nil;
end

function LoadoutUtil:UpdateList()
    for i = 1, #self.buttons do
        self.buttons[i]:Hide();
    end

    local specID = DataProvider:GetCurrentSpecID();
    local configs = C_ClassTalents.GetConfigIDsBySpecID(specID);
    local button;

    for i = 1, #configs do
        button = self.buttons[i];
        if not button then
            button = CreateFrame("Button", nil, self.container, "NarciTalentTreeLoadoutButtonTemplate");
            self.buttons[i] = button;
            button.ButtonText:SetFont(self.font, FONT_HEIGHT, "");
            button:SetPoint("TOP", MainFrame.LoadoutToggle, "TOP", 0, self.buttonHeight*( - i));
            button.Underline:SetHeight(2*PIXEL);
            button.Underline:SetPoint("TOPLEFT", button.ButtonText, "BOTTOMLEFT", 0, -2*PIXEL);
        end
        button:SetConfigID(configs[i]);
        button:Show();
    end

    if button then
        local top = self.container:GetTop();
        local bottom = button:GetBottom();
        self.container:SetHeight(top - bottom + 8);

        if self.activeConfigID then
            self:SetActiveConfigID(self.activeConfigID);
        end
    else
        self.container:SetHeight(64);
    end
end

function LoadoutUtil.ShowFrame_OnUpdate(f, elapsed)
    f.alpha = f.alpha + elapsed * 4;
    if f.alpha > 1 then
        f.alpha = 1;
        f:SetScript("OnUpdate", nil);
    end
    f:SetAlpha(f.alpha);
end

function LoadoutUtil:ShowList()
    if self.Init then
        self:Init();
    end

    self:UpdateList();

    if self.activeButton then
        self.activeButton.Underline.AnimIn:Stop();
        self.activeButton.Underline.AnimIn:Play();
    end

    self.container:Show();
    self.container.alpha = 0;
    self.container:SetScript("OnUpdate", self.ShowFrame_OnUpdate);

    MainFrame.LoadoutToggle.ButtonText:SetText("Loadout");
    MainFrame.LoadoutToggle.Arrow:SetRotation(math.pi);
end

function LoadoutUtil:HideList()
    self.container:Hide();
    MainFrame.LoadoutToggle.Arrow:SetRotation(0);

    if not LoadingBarUtil:IsBarVisible() then
        --MainFrame.LoadoutToggle.ButtonText:SetText(DataProvider:GetActiveLoadoutName());
        MainFrame:ShowActiveBuild();
    end
end

function LoadoutUtil:ToggleList()
    if self.container and self.container:IsShown() then
        self:HideList();
    else
        self:ShowList();
    end
end

function LoadoutUtil:SetActiveConfigID(configID)
    self.activeButton = nil;
    self.activeConfigID = configID;
    if self.buttons then
        for i, b in ipairs(self.buttons) do
            if b.configID == configID then
                b.Underline:Show();
                b.selected = true;
                b.ButtonText:SetTextColor(1, 0.82, 0);
                self.activeButton = b;
            else
                b.Underline:Hide();
                b.selected = false;
                b:OnLeave();
            end
        end
    end
end


NarciTalentTreePvPFrameMixin = {};

function NarciTalentTreePvPFrameMixin:OnShow()
    self:RegisterEvent("PLAYER_PVP_TALENT_UPDATE");
    self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
    self:Update();
end

function NarciTalentTreePvPFrameMixin:OnEvent()
    self:Update();
end

function NarciTalentTreePvPFrameMixin:IsWarModeActive()
    return C_PvP.IsWarModeDesired() or C_PvP.IsWarModeActive()
end

function NarciTalentTreePvPFrameMixin:Update()
    self:Init();

    local talentID, talentInfo;

    if MainFrame:IsInspecting() then
        local unit = MainFrame:GetInspectUnit();
        for i = 1, 3 do
            talentID = C_SpecializationInfo.GetInspectSelectedPvpTalent(unit, i);
            self.slots[i]:SetPvPTalent(talentID, unit);
            self.slots[i]:Show();
            self.slots[i].isInspecting = true;
            self.slots[i]:SetBorderColor(true);
        end

        for i = 4, 6 do
            talentInfo = C_SpecializationInfo.GetPvpTalentSlotInfo(i - 3);
            self.slots[i]:SetPvPTalent((talentInfo ~= nil and talentInfo.selectedTalentID) or nil);
            self.slots[i]:Show();
            self.slots[i].isInspecting = false;
        end
    else
        for i = 4, 6 do
            self.slots[i]:Hide();
            self.slots[i].isInspecting = false;
        end

        for i = 1, 3 do
            talentInfo = C_SpecializationInfo.GetPvpTalentSlotInfo(i);
            self.slots[i]:SetPvPTalent((talentInfo ~= nil and talentInfo.selectedTalentID) or nil);
            self.slots[i]:Show();
            self.slots[i].isInspecting = false;
            self.slots[i]:SetBorderColor(false);
        end
    end
end

function NarciTalentTreePvPFrameMixin:Init()
    if not self.slots then
        self.slots = {};

        local slot;

        for i = 1, 6 do -- 4,5,6 are for inspection (1-3 for the inspectee, 4-6 for me)
            slot = CreateFrame("Button", nil, self, "NarciTalentTreeNodeTemplate");
            self.slots[i] = slot;
            slot.Icon:SetSize(ICON_SIZE, ICON_SIZE);
            slot:SetSize(BUTTON_SIZE, BUTTON_SIZE);
            slot:SetNodeType(0, 1);
            slot.Icon:SetTexCoord(0.0625, 0.9375, 0.0625, 0.9375);
            slot.Symbol:SetVertexColor(0.160, 0.160, 0.160);
            slot:SetScript("OnEnter", ClassTalentTooltipUtil.SetFromPvPButton);

            if i <= 3 then
                slot:SetPoint("TOP", self, "TOP", 0, -HEADER_SIZE -(i + 1) * BUTTON_SIZE);
                slot.index = i;
            else
                slot:SetPoint("TOP", self, "TOP", 0, -HEADER_SIZE -(i + 3) * BUTTON_SIZE);
                slot.index = i - 3;
            end
        end

        self:SetWidth(BUTTON_SIZE * 3);
        self.Divider:ClearAllPoints();
        self.Divider:SetPoint("TOPLEFT", self, "TOPLEFT", 0, -BUTTON_SIZE/2 -HEADER_SIZE);
        self.Divider:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", 0, BUTTON_SIZE/2);
    end
end

function NarciTalentTreePvPFrameMixin:Toggle()
    if self:IsShown() then
        self:Hide();
        MainFrame.PvPTalentToggle.Arrow:SetTexCoord(0, 0.25, 0.5, 1);
    else
        self:Show();
        MainFrame.PvPTalentToggle.Arrow:SetTexCoord(0.25, 0, 0.5, 1);
    end
    LayoutUtil:UpdateFrameSize();
end



if not addon.IsDragonflight() then return end;

local ENABLE_INSPECT = false;
local ENABLE_PAPERDOLL = false;
local HookUtil = {};

function HookUtil:HookInpsectFrame()
    if self.inspectFrameHooked then return end;

    local InspectFrame = _G["InspectFrame"];

    if InspectFrame then
        self.inspectFrameHooked = true;
        InspectFrame:HookScript("OnShow", function()
            if ENABLE_INSPECT and InspectFrame.unit then
                MainFrame:AnchorToInspectFrame();
                MainFrame:Show();
                MainFrame:ShowInspecting(InspectFrame.unit);
            end
        end);

        InspectFrame:HookScript("OnHide", function()
            if ENABLE_INSPECT then
                MainFrame:Hide();
            end
        end);
    else
        if self.inspectFuncHooked then return end;
        self.inspectFuncHooked = true;
        hooksecurefunc("InspectUnit", function()
            if not self.inspectFrameHooked then
                self.inspectFrameHooked = true;
                InspectFrame = _G["InspectFrame"];
                if not InspectFrame then return end;

                InspectFrame:HookScript("OnShow", function()
                    if ENABLE_INSPECT and InspectFrame.unit then
                        MainFrame:AnchorToInspectFrame();
                        MainFrame:Show();
                        MainFrame:ShowInspecting(InspectFrame.unit);
                    end
                end);
    
                InspectFrame:HookScript("OnHide", function()
                    if ENABLE_INSPECT then
                        MainFrame:Hide();
                    end
                end);
            end
        end);
    end
end

function HookUtil:HookPaperDoll()
    if self.paperdollHooked then return end;
    self.paperdollHooked = true;

    local PaperDoll = _G["PaperDollFrame"];
    PaperDoll:HookScript("OnShow", function()
        if ENABLE_PAPERDOLL then
            MainFrame:AnchorToPaperDoll();
            MainFrame:SetInspectionMode(false);
            MainFrame:Show();
            MainFrame:ShowActiveBuild();
        end
    end);

    PaperDoll:HookScript("OnHide", function()
        if ENABLE_PAPERDOLL then
            MainFrame:Hide();
        end
    end);
end


do
    local SettingFunctions = addon.SettingFunctions;

    function SettingFunctions.ShowMiniTalentTreeForInspection(state, db)
        if state == nil then
            state = db["TalentTreeForInspection"];
        end
        if state then
            ENABLE_INSPECT = true;
            HookUtil:HookInpsectFrame();
        else
            ENABLE_INSPECT = false;
            if MainFrame.anchor == "inpsectframe" then
                MainFrame:Hide();
            end
        end
    end

    function SettingFunctions.ShowMiniTalentTreeForPaperDoll(state, db)
        if state == nil then
            state = db["TalentTreeForPaperDoll"];
        end
        if state then
            ENABLE_PAPERDOLL = true;
            HookUtil:HookPaperDoll();
        else
            ENABLE_PAPERDOLL = false;
            if MainFrame.anchor == "paperdoll" then
                MainFrame:Hide();
            end
        end
    end
end