--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent;
local DialogueGroupContainer = require(script.Parent);
local VirtualService = require(root.VirtualService);
local React = require(root.roblox_packages.react);
local ReactErrorBoundary = require(root.roblox_packages.ReactErrorBoundary);
local ErrorBoundary = ReactErrorBoundary.ErrorBoundary;
local ReactRoblox = require(root.roblox_packages["react-roblox"]);
local IJW = require(root.roblox_packages.ijw);
local expect = IJW.expect;
local describe = IJW.describe;
local it = IJW.it;
local VirtualSelection = require(root.DialogueEditor.mocks.Selection);

local screenGui: ScreenGui?;
local reactRoot: ReactRoblox.RootType?;
local propagatedErrorMessage;
local dialogueGroupContainer;
local dialogueGroup;

return {

  describe("DialogueGroupContainer", function()

    local function MockComponent(properties: any)

      local dialogueType = properties.type or "Message";
      local onRendered = properties.onRendered;
      local onErrored = properties.onErrored or function() end;

      React.useEffect(function()

        if onRendered then

          onRendered();

        end;

      end, {});

      return React.createElement(ErrorBoundary, {
        FallbackComponent = React.Fragment;
        onError = onErrored;
      }, {
        React.createElement(DialogueGroupContainer, {
          name = dialogueType;
          plugin = VirtualService.globals.plugin;
          layoutOrder = 1;
        })
      });

    end;

    local function verifyReactStatus()

      if propagatedErrorMessage then

        error(propagatedErrorMessage);

      end;

    end;

    local function getDialogueItems(screenGui: ScreenGui): {Frame}

      dialogueGroupContainer = screenGui:FindFirstChildOfClass("Frame");
      assert(dialogueGroupContainer, "DialogueGroupContainer should be rendered in the ScreenGui.");

      dialogueGroup = dialogueGroupContainer:FindFirstChild("DialogueGroup");
      if not dialogueGroup then

        return {};

      end;

      local dialogueItems = {};
      for _, child in dialogueGroup:GetChildren() do

        if child:IsA("Frame") then

          table.insert(dialogueItems, child);

        end;

      end;

      return dialogueItems;

    end;

    local function createDialogueScript(name: string)

      local dialogueScript = Instance.new("ModuleScript");
      dialogueScript.Name = name;
      dialogueScript:SetAttribute("DialogueType", "Message");
      dialogueScript:AddTag("DialogueMakerDialogueScript");
      return dialogueScript;

    end;

    local function initializeDialogueFolder(messageCount: number)

      local dialogueFolder = Instance.new("Folder");
      dialogueFolder.Name = "Messages";

      for i = 1, messageCount do

        local dialogueScript = createDialogueScript(tostring(i));
        dialogueScript.Parent = dialogueFolder;

      end;

      local selectedScript = Instance.new("ModuleScript");
      selectedScript:SetAttribute("DialogueType", "Message");
      selectedScript:AddTag("DialogueMakerDialogueScript");
      dialogueFolder.Parent = selectedScript;

      return dialogueFolder, selectedScript;

    end;

    local function render()

      assert(reactRoot, "React root should be initialized before running tests.");

      local didRender = false;
      local element = React.createElement(MockComponent, {
        onErrored = function(errorMessage)

          propagatedErrorMessage = errorMessage;

        end;
        onRendered = function()

          didRender = true;

        end;
      });

      reactRoot:render(element);
      repeat task.wait() until didRender or propagatedErrorMessage;

    end;

    return {

      it("refreshes the selected dialogue group if a dialogue script is added to a dialogue folder", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          render();
          verifyReactStatus();

          -- Verify that there are no dialogue items rendered.
          expect(#getDialogueItems(screenGui)).toBe(0);
          assert(dialogueGroupContainer);

          local dialogueFolder, selectedScript = initializeDialogueFolder(4);
          VirtualService.globals.game:GetService("Selection"):Set({selectedScript});
          dialogueGroupContainer.ChildAdded:Wait();
          verifyReactStatus();
          expect(#getDialogueItems(screenGui)).toBe(#dialogueFolder:GetChildren());

          -- Create a new dialogue script, but verify that it is not rendered yet.
          assert(dialogueGroup);

          local newDialogueScript = createDialogueScript(tostring(#dialogueFolder:GetChildren() + 1));
          verifyReactStatus();
          expect(#getDialogueItems(screenGui)).toBe(#dialogueFolder:GetChildren());

          -- Add the new dialogue script to the folder and verify that it is rendered.
          newDialogueScript.Parent = dialogueFolder;
          dialogueGroup.ChildAdded:Wait();
          verifyReactStatus();
          expect(#getDialogueItems(screenGui)).toBe(#dialogueFolder:GetChildren());

        end).toFinishBeforeSeconds(1);

      end);

      it("refreshes the selected dialogue group if a dialogue script is removed from a dialogue folder", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          render();
          verifyReactStatus();

          -- Verify that there are no dialogue items rendered.
          expect(#getDialogueItems(screenGui)).toBe(0);
          assert(dialogueGroupContainer);

          local dialogueFolder, selectedScript = initializeDialogueFolder(4);
          VirtualService.globals.game:GetService("Selection"):Set({selectedScript});
          dialogueGroupContainer.ChildAdded:Wait();
          verifyReactStatus();
          expect(#getDialogueItems(screenGui)).toBe(#dialogueFolder:GetChildren());

          -- Delete a new dialogue script and verify that it is not rendered.
          local dialogueItem = getDialogueItems(screenGui)[1]
          dialogueFolder:GetChildren()[1]:Destroy();
          dialogueItem.Destroying:Wait();
          verifyReactStatus();
          expect(#getDialogueItems(screenGui)).toBe(#dialogueFolder:GetChildren());

        end).toFinishBeforeSeconds(1);

      end);

      it("refreshes the selected dialogue group if a dialogue script's static content is changed", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueFolder, selectedScript = initializeDialogueFolder(4);
          VirtualService.globals.game:GetService("Selection"):Set({selectedScript});
          render();
          verifyReactStatus();

          local dialogueItem = getDialogueItems(screenGui)[1];
          local viewButton = dialogueItem:FindFirstChild("ViewButton");
          assert(viewButton, "ViewButton should be present in the dialogue item.");

          local informationFrame = viewButton:FindFirstChild("InformationFrame");
          assert(informationFrame, "InformationFrame should be present in the ViewButton.");

          local descriptionLabel = informationFrame:FindFirstChild("DescriptionLabel");
          assert(descriptionLabel and descriptionLabel:IsA("TextLabel"), "DescriptionLabel should be present in the InformationFrame.");

          -- Verify that there are no dialogue items rendered.
          local messageContent = "New content";
          dialogueFolder:GetChildren()[1]:SetAttribute("DialogueContent", messageContent);
          verifyReactStatus();
          descriptionLabel:GetPropertyChangedSignal("Text"):Wait();
          expect(descriptionLabel.Text).toBe(messageContent);

        end).toFinishBeforeSeconds(1);

      end);

      it("refreshes the conversation group if a conversation is added", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          render();
          verifyReactStatus();

          -- Select the conversations folder parent and verify that there are no conversations rendered.
          local conversationsFolderParent = Instance.new("Folder");
          local conversationsFolder = Instance.new("Folder");
          conversationsFolder.Name = "Conversations";
          conversationsFolder.Parent = conversationsFolderParent;

          VirtualService.globals.game:GetService("Selection"):Set({conversationsFolderParent});
          verifyReactStatus();
          expect(#getDialogueItems(screenGui)).toBe(#conversationsFolder:GetChildren());

          -- Create a new conversation script, but verify that it is not rendered yet.
          local conversationScript = Instance.new("ModuleScript");
          conversationScript:AddTag("DialogueMakerConversationScript");
          verifyReactStatus();
          expect(#getDialogueItems(screenGui)).toBe(#conversationsFolder:GetChildren());

          -- Add the conversation script to the conversations folder and verify that it is rendered.
          conversationScript.Parent = conversationsFolder;
          verifyReactStatus();
          assert(dialogueGroup);
          dialogueGroup.ChildAdded:Wait();
          expect(#getDialogueItems(screenGui)).toBe(#conversationsFolder:GetChildren());

        end).toFinishBeforeSeconds(1);

      end);

      it("refreshes the conversation group if a conversation is removed", function()

        expect(function()

          -- Select the conversations folder parent.
          local conversationsFolderParent = Instance.new("Folder");
          local conversationsFolder = Instance.new("Folder");
          conversationsFolder.Name = "Conversations";
          conversationsFolder.Parent = conversationsFolderParent;

          local conversationScript = Instance.new("ModuleScript");
          conversationScript:AddTag("DialogueMakerConversationScript");
          conversationScript.Parent = conversationsFolder;
          VirtualService.globals.game:GetService("Selection"):Set({conversationsFolderParent});

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          render();
          verifyReactStatus();
          local dialogueItem = getDialogueItems(screenGui)[1];

          -- Delete the conversation script and verify that it is not rendered.
          conversationScript:Destroy();
          dialogueItem.Destroying:Wait();
          verifyReactStatus();
          expect(#getDialogueItems(screenGui)).toBe(#conversationsFolder:GetChildren());

        end).toFinishBeforeSeconds(1);

      end);

    }

  end, {
    beforeEach = function()

      local newScreenGui = Instance.new("ScreenGui");
      screenGui = newScreenGui;
      reactRoot = ReactRoblox.createRoot(newScreenGui);

      local virtualSelection = VirtualSelection.new();
      VirtualService.globals.game = {
        GetService = function(self, serviceName: string)

          if serviceName == "Selection" then

            return virtualSelection;

          end;

          return game:GetService(serviceName);

        end;
      }

    end;
    afterEach = function()

      if reactRoot then

        reactRoot:unmount();

      end;

      if screenGui then

        screenGui:Destroy();

      end;

      VirtualService.globals.game = game;

      propagatedErrorMessage = nil;
      dialogueGroupContainer = nil;
      dialogueGroup = nil;

    end;
  });

};


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local DialogueGroup = require(script.Parent);
local VirtualService = require(root.VirtualService);
local React = require(root.roblox_packages.react);
local ReactRoblox = require(root.roblox_packages["react-roblox"]);
local IJW = require(root.roblox_packages.ijw);
local expect = IJW.expect;
local describe = IJW.describe;
local it = IJW.it;

local screenGui: ScreenGui?;
local reactRoot: ReactRoblox.RootType?;

return {
  describe("DialogueGroup", function()

    local function MockComponent(properties: any)

      local dialogueType = properties.type or "Message";
      local onRendered = properties.onRendered;
      local dialogueScripts = properties.dialogueScripts or {};
      local layoutOrder = properties.layoutOrder or 1;

      React.useEffect(function()

        if onRendered then

          onRendered();

        end;

      end, {});

      return React.createElement(DialogueGroup, {
        name = dialogueType;
        plugin = VirtualService.globals.plugin;
        dialogueScripts = dialogueScripts;
        layoutOrder = layoutOrder;
      });

    end;

    return {

      it("creates a DialogueItem component for each child dialogue of the selected script", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          assert(reactRoot, "React root should be initialized before running tests.");

          local renderedEvent = Instance.new("BindableEvent");
          local messages = {"This should be a message", "This should be another message", "This should be a third message", "This should be a fourth message"};
          local dialogueScripts = {};
          for _, message in messages do

            local dialogueScript = Instance.new("ModuleScript");
            dialogueScript.Name = tostring(#dialogueScripts + 1);
            dialogueScript:SetAttribute("DialogueContent", message);
            table.insert(dialogueScripts, dialogueScript);

          end;

          local element = React.createElement(MockComponent, {
            dialogueScripts = dialogueScripts;
            onRendered = function()

              renderedEvent:Fire();

            end;
          });

          reactRoot:render(element);
          renderedEvent.Event:Wait();

          -- Simulate a click on the component.
          local dialogueGroup = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueGroup, "DialogueGroup should be rendered in the ScreenGui.");

          local dialogueItems = {};
          for _, child in dialogueGroup:GetChildren() do

            if child:IsA("Frame") then

              table.insert(dialogueItems, child);

              local viewButton = child:FindFirstChild("ViewButton");
              assert(viewButton and viewButton:IsA("GuiButton"), "ViewButton should be present in the DialogueItem.");

              local informationFrame = viewButton:FindFirstChild("InformationFrame");
              assert(informationFrame, "Information should be present in the ViewButton.");

              local descriptionLabel = informationFrame:FindFirstChild("DescriptionLabel");
              assert(descriptionLabel and descriptionLabel:IsA("TextLabel"), "DescriptionLabel should be present in the InformationFrame.");

              local expectedMessage = messages[child.LayoutOrder];
              expect(descriptionLabel.Text).toBe(expectedMessage);

            end;

          end;

          expect(#dialogueItems).toBe(4);

        end).toFinishBeforeSeconds(1);

      end);

    }

  end, {
    beforeEach = function()

      local newScreenGui = Instance.new("ScreenGui");
      screenGui = newScreenGui;
      reactRoot = ReactRoblox.createRoot(newScreenGui);

    end;
    afterEach = function()

      if reactRoot then

        reactRoot:unmount();

      end;

      if screenGui then

        screenGui:Destroy();

      end;

    end;
  })
};


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local DialogueItem = require(script.Parent);
local VirtualService = require(root.VirtualService);
local VirtualSelection = require(root.DialogueEditor.mocks.Selection);
local VirtualChangeHistoryService = require(root.DialogueEditor.mocks.ChangeHistoryService);
local React = require(root.roblox_packages.react);
local ReactErrorBoundary = require(root.roblox_packages.ReactErrorBoundary);
local ErrorBoundary = ReactErrorBoundary.ErrorBoundary;
local ReactRoblox = require(root.roblox_packages["react-roblox"]);
local IJW = require(root.roblox_packages.ijw);
local expect = IJW.expect;
local describe = IJW.describe;
local it = IJW.it;

local screenGui: ScreenGui?;
local reactRoot: ReactRoblox.RootType?;

return {
  describe("DialogueItem", function()

    local function MockComponent(properties: any)

      local dialogueType = properties.type or "Message";
      local onRendered = properties.onRendered;
      local dialogueScript = properties.dialogueScript or Instance.new("ModuleScript");
      local layoutOrder = properties.layoutOrder or 1;
      local onErrored = properties.onErrored or function() end;

      React.useEffect(function()

        if onRendered then

          onRendered();

        end;

      end, {});

      return React.createElement(ErrorBoundary, {
        FallbackComponent = React.Fragment;
        onError = onErrored;
      }, {
        React.createElement(DialogueItem, {
          type = dialogueType;
          dialogueScript = dialogueScript;
          dialogueScriptCount = if dialogueScript.Parent then #dialogueScript.Parent:GetChildren() else 1;
          layoutOrder = layoutOrder;
          setSettingsTarget = function() end;
        })
      });

    end;

    local function generateDialogueContainer()

      local dialogueContainer = Instance.new("Folder");
      local dialogueScript = Instance.new("ModuleScript");
      dialogueScript.Name = "1";
      dialogueScript:AddTag("DialogueMakerDialogueScript");
      dialogueScript.Parent = dialogueContainer;

      local secondDialogueScript = Instance.new("ModuleScript");
      secondDialogueScript.Name = "2";
      secondDialogueScript:AddTag("DialogueMakerDialogueScript");
      secondDialogueScript.Parent = dialogueContainer;

      return dialogueContainer;

    end;

    return {

      it("can set selection on click", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          assert(reactRoot, "React root should be initialized before running tests.");

          local didRender = false;
          local propagatedErrorMessage;
          local element = React.createElement(MockComponent, {
            onErrored = function(errorMessage)

              propagatedErrorMessage = errorMessage;

            end;
            onRendered = function()

              didRender = true;

            end;
          });

          reactRoot:render(element);
          repeat task.wait() until didRender or propagatedErrorMessage;

          if propagatedErrorMessage then

            error(propagatedErrorMessage);

          end;

          -- Simulate a click on the component.
          local dialogueItem = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueItem, "DialogueItem should be rendered in the ScreenGui.");

          local viewButton = dialogueItem:FindFirstChild("ViewButton");
          assert(viewButton and viewButton:IsA("GuiButton"), "ViewButton should be present in the DialogueItem.");

          local didSelectionChange = false;
          VirtualService.globals.game:GetService("Selection").SelectionChanged:Once(function()

            didSelectionChange = true;

          end);

          VirtualService.events.GuiButton.Activated:fireEvent(viewButton);

          while not didSelectionChange do task.wait() end;

        end).toFinishBeforeSeconds(1);

      end);

      it("can reduce dialogue priority", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          assert(reactRoot, "React root should be initialized before running tests.");

          local renderedEvent = Instance.new("BindableEvent");
          local dialogueContainer = generateDialogueContainer();
          local dialogueScript = dialogueContainer:FindFirstChild("1");
          assert(dialogueScript, "Dialogue script should be present in the dialogue container.");

          local element = React.createElement(MockComponent, {
            dialogueScript = dialogueScript;
            layoutOrder = 1;
            onRendered = function()

              renderedEvent:Fire();

            end;
          });

          reactRoot:render(element);
          renderedEvent.Event:Wait();

          -- Simulate a click on the component.
          local dialogueItem = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueItem, "DialogueItem should be rendered in the ScreenGui.");

          local decreasePriorityButton = dialogueItem:FindFirstChild("DecreasePriorityButton");
          assert(decreasePriorityButton and decreasePriorityButton:IsA("GuiButton"), "DecreasePriorityButton should be present in the DialogueItem.");

          local didPriorityChange = false;
          dialogueScript:GetPropertyChangedSignal("Name"):Once(function()

            expect(dialogueScript.Name).toBe("2");
            didPriorityChange = true;

          end);

          VirtualService.events.GuiButton.Activated:fireEvent(decreasePriorityButton);

          while not didPriorityChange do task.wait() end;

        end).toFinishBeforeSeconds(1);

      end);

      it("can increase dialogue priority", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          assert(reactRoot, "React root should be initialized before running tests.");

          local renderedEvent = Instance.new("BindableEvent");
          local dialogueContainer = generateDialogueContainer();
          local dialogueScript = dialogueContainer:FindFirstChild("2");
          assert(dialogueScript, "Dialogue script should be present in the dialogue container.");

          local element = React.createElement(MockComponent, {
            dialogueScript = dialogueScript;
            layoutOrder = 2;
            onRendered = function()

              renderedEvent:Fire();

            end;
          });

          reactRoot:render(element);
          renderedEvent.Event:Wait();

          -- Simulate a click on the component.
          local dialogueItem = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueItem, "DialogueItem should be rendered in the ScreenGui.");

          local increasePriorityButton = dialogueItem:FindFirstChild("IncreasePriorityButton");
          assert(increasePriorityButton and increasePriorityButton:IsA("GuiButton"), "IncreasePriorityButton should be present in the DialogueItem.");

          local didPriorityChange = false;
          dialogueScript:GetPropertyChangedSignal("Name"):Once(function()

            expect(dialogueScript.Name).toBe("1");
            didPriorityChange = true;

          end);

          VirtualService.events.GuiButton.Activated:fireEvent(increasePriorityButton);

          while not didPriorityChange do task.wait() end;

        end).toFinishBeforeSeconds(1);

      end);

      it("cannot modify priority of conversations", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          assert(reactRoot, "React root should be initialized before running tests.");

          local renderedEvent = Instance.new("BindableEvent");
          local element = React.createElement(MockComponent, {
            type = "Conversation";
            onRendered = function()

              renderedEvent:Fire();

            end;
          });

          reactRoot:render(element);
          renderedEvent.Event:Wait();

          -- Verify that the increase and decrease priority buttons are not present.
          local dialogueItem = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueItem, "DialogueItem should be rendered in the ScreenGui.");

          local increasePriorityButton = dialogueItem:FindFirstChild("IncreasePriorityButton");
          expect(increasePriorityButton).toBeNil();

          local decreasePriorityButton = dialogueItem:FindFirstChild("DecreasePriorityButton");
          expect(decreasePriorityButton).toBeNil();

        end).toFinishBeforeSeconds(1);

      end);

      it("can preview static dialogue content string", function()

        expect(function()

          -- Render the component and wait for it to finish rendering.
          assert(screenGui, "ScreenGui should be initialized before running tests.");
          assert(reactRoot, "React root should be initialized before running tests.");

          local expectedMessage = "This is a static dialogue content string.";
          local renderedEvent = Instance.new("BindableEvent");
          local dialogueScript = Instance.new("ModuleScript");
          dialogueScript.Name = "1";
          dialogueScript:SetAttribute("DialogueContent", expectedMessage);

          local element = React.createElement(MockComponent, {
            dialogueScript = dialogueScript;
            onRendered = function()

              renderedEvent:Fire();

            end;
          });

          reactRoot:render(element);
          renderedEvent.Event:Wait();

          -- Simulate a click on the component.
          local dialogueItem = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueItem, "DialogueItem should be rendered in the ScreenGui.");

          local viewButton = dialogueItem:FindFirstChild("ViewButton");
          assert(viewButton and viewButton:IsA("GuiButton"), "ViewButton should be present in the DialogueItem.");

          local informationFrame = viewButton:FindFirstChild("InformationFrame");
          assert(informationFrame, "Information should be present in the ViewButton.");

          local descriptionLabel = informationFrame:FindFirstChild("DescriptionLabel");
          assert(descriptionLabel and descriptionLabel:IsA("TextLabel"), "DescriptionLabel should be present in the InformationFrame.");
          expect(descriptionLabel.Text).toBe(expectedMessage);

        end).toFinishBeforeSeconds(1);

      end);

    }

  end, {
    beforeEach = function()

      local newScreenGui = Instance.new("ScreenGui");
      screenGui = newScreenGui;
      reactRoot = ReactRoblox.createRoot(newScreenGui);

      local virtualSelection = VirtualSelection.new();
      local virtualChangeHistoryService = VirtualChangeHistoryService.new();
      VirtualService.globals.game = {
        GetService = function(self, serviceName: string): unknown

          if serviceName == "Selection" then

            return virtualSelection;

          elseif serviceName == "ChangeHistoryService" then

            return virtualChangeHistoryService;

          end;

          return VirtualService.globals.game:GetService(serviceName);

        end;
      }

    end;
    afterEach = function()

      VirtualService.globals.game = game;

      if reactRoot then

        reactRoot:unmount();

      end;

      if screenGui then

        screenGui:Destroy();

      end;

    end;
  })
};


--!strict

local HttpService = game:GetService("HttpService");

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local DialogueItem = require(script.components.DialogueItem);

export type DialogueItemType = DialogueItem.DialogueItemType;

export type DialogueGroupProperties = {
  name: DialogueItemType;
  plugin: Plugin;
  dialogueScripts: {ModuleScript};
  layoutOrder: number;
}

local function DialogueGroup(properties: DialogueGroupProperties)

  local groupName = properties.name;
  local dialogueScripts = properties.dialogueScripts;
  local layoutOrder = properties.layoutOrder;
  local plugin = properties.plugin;

  local scriptComponents = {};
  for index, dialogueScript in dialogueScripts do

    -- All I want for Christmas is Instance.UniqueID
    local dialogueUniqueID = dialogueScript:GetAttribute("DialogueUniqueID");
    if not dialogueUniqueID then

      dialogueUniqueID = HttpService:GenerateGUID(false);
      dialogueScript:SetAttribute("DialogueUniqueID", dialogueUniqueID);

    end;

    local settingComponent = React.createElement(DialogueItem, {
      key = dialogueUniqueID;
      plugin = plugin;
      dialogueScript = dialogueScript;
      dialogueScriptCount = #dialogueScripts;
      layoutOrder = index;
      type = groupName :: DialogueItemType;
    });

    table.insert(scriptComponents, settingComponent);

  end;

  return React.createElement("Frame", {
    AutomaticSize = Enum.AutomaticSize.Y;
    BackgroundTransparency = 1;
    LayoutOrder = layoutOrder;
    Size = UDim2.fromScale(1, 0);
  }, {
    UIListLayout = React.createElement("UIListLayout", {
      SortOrder = Enum.SortOrder.LayoutOrder;
      Padding = UDim.new(0, 10);
    });
    ScriptList = React.createElement(React.Fragment, {}, scriptComponents);
  });

end;

return React.memo(DialogueGroup);


--!strict

local StarterPlayer = game:GetService("StarterPlayer");

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local AutoTriggerCheckbox = require(script.Parent);
local VirtualService = require(root.VirtualService);
local VirtualChangeHistoryService = require(root.DialogueEditor.mocks.ChangeHistoryService);
local React = require(root.roblox_packages.react);
local ReactErrorBoundary = require(root.roblox_packages.ReactErrorBoundary);
local ErrorBoundary = ReactErrorBoundary.ErrorBoundary;
local ReactRoblox = require(root.roblox_packages["react-roblox"]);
local IJW = require(root.roblox_packages.ijw);
local expect = IJW.expect;
local describe = IJW.describe;
local it = IJW.it;

local screenGui: ScreenGui?;
local reactRoot: ReactRoblox.RootType?;
local propagatedErrorMessage;
local originalLoaderScript;
local childAddedConnection: RBXScriptConnection?;

return {

  describe("AutoTriggerCheckbox", function()

    local function MockComponent(properties: any)

      local selectedScript = properties.selectedScript;
      local onErrored = properties.onErrored or function() end;
      local onRendered = properties.onRendered or function() end;

      React.useEffect(function()

        onRendered();

      end, {onRendered});

      return React.createElement(ErrorBoundary, {
        FallbackComponent = React.Fragment;
        onError = onErrored;
      }, {
        React.createElement(AutoTriggerCheckbox, {
          selectedScript = selectedScript;
          layoutOrder = 1;
        })
      });

    end;

    local function verifyReactStatus()

      if propagatedErrorMessage then

        error(propagatedErrorMessage);

      end;

    end;

    local function createDialogueScript(name: string)

      local dialogueScript = Instance.new("ModuleScript");
      dialogueScript:AddTag("DialogueMakerConversationScript");
      return dialogueScript;

    end;

    local function render(initialValue: boolean?): ModuleScript

      assert(reactRoot, "React root should be initialized before running tests.");

      local selectedScript = createDialogueScript("TestDialogueScript");
      selectedScript:SetAttribute("ShouldAutoTriggerConversation", initialValue);

      local didRender = false;
      local element = React.createElement(MockComponent, {
        selectedScript = selectedScript;
        onErrored = function(errorMessage)

          propagatedErrorMessage = errorMessage;

        end;
        onRendered = function()

          didRender = true;

        end;
      });

      reactRoot:render(element);
      repeat task.wait() until didRender or propagatedErrorMessage;

      return selectedScript;

    end;

    local function getCheckbox()

      assert(screenGui, "ScreenGui should be initialized before running tests.");

      local checkboxFrame = screenGui:FindFirstChildOfClass("Frame");
      assert(checkboxFrame, "Frame should be present in the ScreenGui.");

      local checkbox = checkboxFrame:FindFirstChild("Checkbox");
      assert(checkbox and checkbox:IsA("TextButton"), "Checkbox should be present in the frame.");

      return checkbox;

    end;

    local function verifyCheckboxFeature(goalValue: boolean)

      local selectedScript = render(not goalValue);
      verifyReactStatus();

      local checkbox = getCheckbox();

      VirtualService.events.GuiButton.Activated:fireEvent(checkbox);
      expect(selectedScript:GetAttribute("ShouldAutoTriggerConversation")).toBe(goalValue);

    end;

    return {

      it(`can add "ShouldAutoTriggerConversation" tag to selected script`, function()

        expect(function()

          verifyCheckboxFeature(true);

        end).toFinishBeforeSeconds(1);

      end);

      it(`can remove "ShouldAutoTriggerConversation" tag from selected script`, function()

        expect(function()

          verifyCheckboxFeature(false);

        end).toFinishBeforeSeconds(1);

      end);

      it(`automatically refreshes if ShouldAutoTriggerConversation attribute is toggled`, function()

        expect(function()

          local selectedScript = render();
          verifyReactStatus();

          local checkbox = getCheckbox();
          selectedScript:SetAttribute("ShouldAutoTriggerConversation", true);
          checkbox:GetPropertyChangedSignal("BackgroundColor3"):Wait();

          selectedScript:SetAttribute("ShouldAutoTriggerConversation", false);
          checkbox:GetPropertyChangedSignal("BackgroundColor3"):Wait();

        end).toFinishBeforeSeconds(1);

      end);

      it("can automatically create loader script from template", function()

        expect(function()

          verifyCheckboxFeature(true);

          local createdLoaderScript = StarterPlayer.StarterPlayerScripts:FindFirstChild("DialogueLoader");

          expect(createdLoaderScript).toNotBe(nil);

          createdLoaderScript:Destroy();

        end).toFinishBeforeSeconds(1);

      end);

    }

  end, {
    beforeAll = function()

      local loaderScript = StarterPlayer.StarterPlayerScripts:FindFirstChild("DialogueMakerLoader");
      if loaderScript then

        originalLoaderScript = loaderScript;
        loaderScript.Parent = nil;

      end;

    end,
    beforeEach = function()

      local newScreenGui = Instance.new("ScreenGui");
      screenGui = newScreenGui;
      reactRoot = ReactRoblox.createRoot(newScreenGui);

      local virtualChangeHistoryService = VirtualChangeHistoryService.new();
      VirtualService.globals.game = {
        GetService = function(self, serviceName: string): unknown

          if serviceName == "ChangeHistoryService" then

            return virtualChangeHistoryService;

          end;

          return game:GetService(serviceName);

        end;
      };

    end;
    afterEach = function()

      if reactRoot then

        reactRoot:unmount();

      end;

      if screenGui then

        screenGui:Destroy();

      end;

      propagatedErrorMessage = nil;

    end;
    afterAll = function()

      if originalLoaderScript then

        originalLoaderScript.Parent = StarterPlayer.StarterPlayerScripts;

      end;

      if childAddedConnection then

        childAddedConnection:Disconnect();
        childAddedConnection = nil;

      end;

    end;
  })
};


--!strict

local CollectionService = game:GetService("CollectionService");
local StarterPlayer = game:GetService("StarterPlayer");

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local Checkbox = require(root.DialogueEditor.components.Checkbox);
local useChangeHistory = require(root.DialogueEditor.hooks.useChangeHistory);

export type AutoTriggerCheckboxProperties = {
  layoutOrder: number;
  selectedScript: ModuleScript;
}

local function AutoTriggerCheckbox(properties: AutoTriggerCheckboxProperties)

  local selectedScript = properties.selectedScript;
  local layoutOrder = properties.layoutOrder;
  local shouldAutoTriggerConversation, setShouldAutoTriggerConversation = React.useState(selectedScript:GetAttribute("ShouldAutoTriggerConversation"));
  local beginHistoryRecording, finishHistoryRecording = useChangeHistory();

  React.useEffect(function()

    local autoTriggerConnection = selectedScript:GetAttributeChangedSignal("ShouldAutoTriggerConversation"):Connect(function()

      setShouldAutoTriggerConversation(selectedScript:GetAttribute("ShouldAutoTriggerConversation"));

    end);

    return function()

      autoTriggerConnection:Disconnect();

    end;

  end, {selectedScript});

  return React.createElement(Checkbox, {
    text = "Auto-trigger";
    isChecked = shouldAutoTriggerConversation;
    layoutOrder = layoutOrder;
    onChanged = function(isChecked: boolean)

      local historyIdentifier = beginHistoryRecording("Set auto-trigger conversation");

      selectedScript:SetAttribute("ShouldAutoTriggerConversation", isChecked);

      local loaderScript: Instance? = CollectionService:GetTagged("DialogueMakerLoader")[1];
      if isChecked then

        if not loaderScript then

          local newLoaderScript = root.Templates.LoaderTemplate:Clone();
          newLoaderScript.Name = "DialogueLoader";
          newLoaderScript:AddTag("DialogueMakerLoader");
          newLoaderScript.Parent = StarterPlayer.StarterPlayerScripts;
          loaderScript = newLoaderScript;

        end;

        assert(loaderScript and loaderScript:IsA("LocalScript"), "Loader script not found even after creating a new one. Huh!?");

        loaderScript.Enabled = true;

      elseif loaderScript and loaderScript:IsA("LocalScript") then

        -- Don't disable the loader script if any conversation is still set to auto-trigger.
        for _, conversationScript in CollectionService:GetTagged("DialogueMakerConversationScript") do

          if conversationScript:GetAttribute("ShouldAutoTriggerConversation") then

            return;

          end;

        end;

        loaderScript.Enabled = false;

      end;

      finishHistoryRecording(historyIdentifier);

    end;
  });

end;

return React.memo(AutoTriggerCheckbox);


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local useStudioColors = require(root.DialogueEditor.hooks.useStudioColors);

export type ContentPreviewProperties = {
  layoutOrder: number;
  children: React.ReactNode;
}

local function ContentPreview(properties: ContentPreviewProperties)

  local colors = useStudioColors();
  local children = properties.children;
  local layoutOrder = properties.layoutOrder;

  return React.createElement("Frame", {
    BackgroundTransparency = 1;
    LayoutOrder = layoutOrder;
    Size = UDim2.new(1, 0, 0, 100);
  }, {
    UICorner = React.createElement("UICorner", {
      CornerRadius = UDim.new(0, 5);
    });
    UIStroke = React.createElement("UIStroke", {
      Color = colors.border;
      Thickness = 1;
      ApplyStrokeMode = Enum.ApplyStrokeMode.Border;
    });
    UIListLayout = React.createElement("UIListLayout", {
      SortOrder = Enum.SortOrder.LayoutOrder;
      VerticalAlignment = Enum.VerticalAlignment.Center;
      HorizontalAlignment = Enum.HorizontalAlignment.Center;
      Padding = UDim.new(0, 10);
    });
    UIPadding = React.createElement("UIPadding", {
      PaddingTop = UDim.new(0, 15);
      PaddingBottom = UDim.new(0, 15);
      PaddingLeft = UDim.new(0, 15);
      PaddingRight = UDim.new(0, 15);
    });
    Children = React.createElement(React.Fragment, {}, children);
  });

end;

return React.memo(ContentPreview);


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local DialogueOptions = require(script.Parent);
local VirtualService = require(root.VirtualService);
local React = require(root.roblox_packages.react);
local ReactErrorBoundary = require(root.roblox_packages.ReactErrorBoundary);
local ErrorBoundary = ReactErrorBoundary.ErrorBoundary;
local ReactRoblox = require(root.roblox_packages["react-roblox"]);
local IJW = require(root.roblox_packages.ijw);
local expect = IJW.expect;
local describe = IJW.describe;
local it = IJW.it;
local VirtualSelection = require(root.DialogueEditor.mocks.Selection);
local VirtualChangeHistoryService = require(root.DialogueEditor.mocks.ChangeHistoryService);

local screenGui: ScreenGui?;
local reactRoot: ReactRoblox.RootType?;
local propagatedErrorMessage;
local eventConnection: RBXScriptConnection?;
local didOpenScript = false;

return {

  describe("DialogueOptions", function()

    local function MockComponent(properties: any)

      local selectedScript = properties.selectedScript;
      local onErrored = properties.onErrored or function() end;
      local onRendered = properties.onRendered or function() end;

      React.useEffect(function()

        onRendered();

      end, {onRendered});

      return React.createElement(ErrorBoundary, {
        FallbackComponent = React.Fragment;
        onError = onErrored;
      }, {
        React.createElement(DialogueOptions, {
          selectedScript = selectedScript;
          plugin = VirtualService.globals.plugin;
          selectedDialogueType = selectedScript:GetAttribute("DialogueType") or "Conversation";
          layoutOrder = 1;
        })
      });

    end;

    local function verifyReactStatus()

      if propagatedErrorMessage then

        error(propagatedErrorMessage);

      end;

    end;

    local function createDialogueScript(name: string, type: "Conversation" | "Message" | "Redirect")

      local dialogueScript = Instance.new("ModuleScript");

      if type == "Conversation" then

        dialogueScript:AddTag("DialogueMakerConversationScript");

      else

        dialogueScript:AddTag("DialogueMakerDialogueScript");
        dialogueScript:SetAttribute("DialogueType", type);

      end;

      return dialogueScript;

    end;

    local function render(type: "Conversation" | "Message" | "Redirect"): ModuleScript

      assert(reactRoot, "React root should be initialized before running tests.");

      local selectedScript = createDialogueScript("TestDialogueScript", type);
      local didRender = false;
      local element = React.createElement(MockComponent, {
        selectedScript = selectedScript;
        onErrored = function(errorMessage)

          propagatedErrorMessage = errorMessage;

        end;
        onRendered = function()

          didRender = true;

        end;
      });

      reactRoot:render(element);
      repeat task.wait() until didRender or propagatedErrorMessage;
      verifyReactStatus();

      return selectedScript;

    end;

    return {

      it("can delete selected script", function()

        expect(function()

          local selectedScript = render("Conversation");
          local testFolder = Instance.new("Folder");
          selectedScript.Parent = testFolder;

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueOptions = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueOptions, "DialogueOptions should be rendered.");

          local deleteButton = dialogueOptions:FindFirstChild("DeleteButton");
          assert(deleteButton and deleteButton:IsA("TextButton"), "Delete button should be present in the DialogueOptions.");

          local didChangeParent = false;
          eventConnection = selectedScript:GetPropertyChangedSignal("Parent"):Once(function()

            didChangeParent = true;

          end);
          VirtualService.events.GuiButton.Activated:fireEvent(deleteButton);
          repeat task.wait() until didChangeParent or propagatedErrorMessage;
          verifyReactStatus();
          expect(selectedScript.Parent).toBe(nil);

        end).toFinishBeforeSeconds(1);

      end);

      it("hides condition and action buttons for conversations", function()

        expect(function()

          render("Conversation");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueOptions = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueOptions, "DialogueOptions should be rendered.");

          local conditionButton = dialogueOptions:FindFirstChild("ConditionButton");
          expect(conditionButton).toBe(nil);

          local actionsDropdown = dialogueOptions:FindFirstChild("ActionsDropdown");
          expect(actionsDropdown).toBe(nil);

        end).toFinishBeforeSeconds(1);

      end);

      it("shows condition and action buttons for dialogue", function()

        expect(function()

          render("Message");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueOptions = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueOptions, "DialogueOptions should be rendered.");

          local conditionButton = dialogueOptions:FindFirstChild("ConditionButton");
          expect(conditionButton).toNotBe(nil);

          local actionsDropdown = dialogueOptions:FindFirstChild("ActionsDropdown");
          expect(actionsDropdown).toNotBe(nil);

        end).toFinishBeforeSeconds(1);

      end);

      it(`hides non-initialization actions for "Redirect"-type dialogue scripts`, function()

        expect(function()

          render("Redirect");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueOptions = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueOptions, "DialogueOptions should be rendered.");

          local actionsDropdown = dialogueOptions:FindFirstChild("ActionsDropdown");
          assert(actionsDropdown, "Actions dropdown should be present in the DialogueOptions.");

          local toggleButton = actionsDropdown:FindFirstChild("ToggleButton");
          assert(toggleButton and toggleButton:IsA("TextButton"), "Toggle button should be present in the ActionsDropdown.");

          VirtualService.events.GuiButton.Activated:fireEvent(toggleButton);

          local optionsFrame = actionsDropdown:WaitForChild("OptionsFrame");
          expect(optionsFrame:FindFirstChild("InitializationButton")).toNotBe(nil);
          expect(optionsFrame:FindFirstChild("CompletionButton")).toBe(nil);
          expect(optionsFrame:FindFirstChild("CleanupButton")).toBe(nil);

        end).toFinishBeforeSeconds(1);

      end);

      it("can initialize a condition script", function()

        expect(function()

          local selectedScript = render("Message");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueOptions = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueOptions, "DialogueOptions should be rendered.");

          local conditionButton = dialogueOptions:FindFirstChild("ConditionButton");
          assert(conditionButton and conditionButton:IsA("TextButton"), "Condition button should be present in the DialogueOptions.");
          VirtualService.events.GuiButton.Activated:fireEvent(conditionButton);
          selectedScript:WaitForChild("ConditionScript");

        end).toFinishBeforeSeconds(1);

      end);

      it("can initialize action scripts", function()

        expect(function()

          local selectedScript = render("Message");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueOptions = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueOptions, "DialogueOptions should be rendered.");

          local actionsDropdown = dialogueOptions:FindFirstChild("ActionsDropdown");
          assert(actionsDropdown, "Actions dropdown should be present in the DialogueOptions.");

          local toggleButton = actionsDropdown:FindFirstChild("ToggleButton");
          assert(toggleButton and toggleButton:IsA("TextButton"), "Toggle button should be present in the ActionsDropdown.");

          VirtualService.events.GuiButton.Activated:fireEvent(toggleButton);

          local optionsFrame = actionsDropdown:WaitForChild("OptionsFrame");
          local initializationButton = optionsFrame:FindFirstChild("InitializationButton");
          assert(initializationButton and initializationButton:IsA("TextButton"), "Initialization button should be present in the ActionsDropdown options.");

          VirtualService.events.GuiButton.Activated:fireEvent(initializationButton);
          selectedScript:WaitForChild("InitializationActionScript");

        end).toFinishBeforeSeconds(1);

      end);

      it("can request to open condition script in Roblox script editor", function()

        expect(function()

          render("Message");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueOptions = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueOptions, "DialogueOptions should be rendered.");

          local conditionButton = dialogueOptions:FindFirstChild("ConditionButton");
          assert(conditionButton and conditionButton:IsA("TextButton"), "Condition button should be present in the DialogueOptions.");
          VirtualService.events.GuiButton.Activated:fireEvent(conditionButton);
          repeat task.wait() until didOpenScript or propagatedErrorMessage;

        end).toFinishBeforeSeconds(1);

      end);

      it("can request to open action script in Roblox script editor", function()

        expect(function()

          render("Message");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dialogueOptions = screenGui:FindFirstChildOfClass("Frame");
          assert(dialogueOptions, "DialogueOptions should be rendered.");

          local actionsDropdown = dialogueOptions:FindFirstChild("ActionsDropdown");
          assert(actionsDropdown, "Actions dropdown should be present in the DialogueOptions.");

          local toggleButton = actionsDropdown:FindFirstChild("ToggleButton");
          assert(toggleButton and toggleButton:IsA("TextButton"), "Toggle button should be present in the ActionsDropdown.");

          VirtualService.events.GuiButton.Activated:fireEvent(toggleButton);

          local optionsFrame = actionsDropdown:WaitForChild("OptionsFrame");
          local initializationButton = optionsFrame:FindFirstChild("InitializationButton");
          assert(initializationButton and initializationButton:IsA("TextButton"), "Initialization button should be present in the ActionsDropdown options.");

          VirtualService.events.GuiButton.Activated:fireEvent(initializationButton);
          repeat task.wait() until didOpenScript or propagatedErrorMessage;

        end).toFinishBeforeSeconds(1);

      end);

    }

  end, {
    beforeEach = function()

      local newScreenGui = Instance.new("ScreenGui");
      screenGui = newScreenGui;
      reactRoot = ReactRoblox.createRoot(newScreenGui);

      local virtualSelection = VirtualSelection.new();
      local virtualChangeHistoryService = VirtualChangeHistoryService.new();
      VirtualService.globals.game = {
        GetService = function(self, serviceName: string): unknown

          if serviceName == "Selection" then

            return virtualSelection;

          elseif serviceName == "ChangeHistoryService" then

            return virtualChangeHistoryService;

          end;

          return game:GetService(serviceName);

        end;
      };

      VirtualService.globals.plugin = {
        OpenScript = function(script)

          didOpenScript = true;

        end
      } :: any;

    end;
    afterEach = function()

      if reactRoot then

        reactRoot:unmount();
        reactRoot = nil;

      end;

      if screenGui then

        screenGui:Destroy();
        screenGui = nil;

      end;

      if eventConnection then

        eventConnection:Disconnect();
        eventConnection = nil;

      end;

      propagatedErrorMessage = nil;
      VirtualService.globals.game = game;
      VirtualService.globals.plugin = plugin;
      didOpenScript = false;

    end;
  })
};


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local Button = require(root.DialogueEditor.components.Button);
local Dropdown = require(root.DialogueEditor.components.Dropdown);
local DropdownOption = require(root.DialogueEditor.components.DropdownOption);
local useStudioColors = require(root.DialogueEditor.hooks.useStudioColors);
local useDialogueScriptType = require(root.DialogueEditor.hooks.useDialogueScriptType);
local useChangeHistory = require(root.DialogueEditor.hooks.useChangeHistory);
local VirtualService = require(root.VirtualService);

type DialogueScriptType = useDialogueScriptType.DialogueScriptType;

export type DialogueOptionsProperties = {
  selectedScript: ModuleScript;
  selectedDialogueType: DialogueScriptType;
  layoutOrder: number;
  plugin: Plugin;
}

local function DialogueOptions(properties: DialogueOptionsProperties)

  local Selection = VirtualService.globals.game:GetService("Selection");

  local selectedScript = properties.selectedScript;
  local selectedDialogueType = properties.selectedDialogueType;
  local layoutOrder = properties.layoutOrder;
  local plugin = properties.plugin;
  local colors = useStudioColors();
  local beginHistoryRecording, finishHistoryRecording = useChangeHistory();

  local dialogueOptions: {React.ReactNode} = {};
  local isActionsDropdownOpen, setIsActionsDropdownOpen = React.useState(false);
  if selectedDialogueType ~= "Conversation" then

    local conditionButton = React.createElement(Button, {
      key = "ConditionButton";
      text = "Edit condition";
      layoutOrder = #dialogueOptions + 1;
      onClick = function()

        local conditionScript = selectedScript:FindFirstChild("ConditionScript");
        if not conditionScript then

          local historyIdentifier = beginHistoryRecording("Add condition script");

          local newConditionScript = root.Templates.DialogueConditionScriptTemplate:Clone();
          newConditionScript.Name = "ConditionScript";
          newConditionScript.Parent = selectedScript;
          conditionScript = newConditionScript;

          finishHistoryRecording(historyIdentifier);

        end;

        assert(conditionScript and conditionScript:IsA("ModuleScript"), `Condition script not found for {selectedScript:GetFullName()}.`);

        plugin:OpenScript(conditionScript);

      end;
    });

    table.insert(dialogueOptions, conditionButton);

    local actionsDropdownOptions = {};
    local actionTypes = {"Initialization", "Completion", "Cleanup"};
    for index, actionType in actionTypes do

      -- Redirects only have initialization actions.
      if selectedDialogueType == "Redirect" and actionType ~= "Initialization" then

        continue;

      end;

      local option = React.createElement(DropdownOption, {
        key = `{actionType}Button`;
        text = actionType :: string;
        layoutOrder = index;
        onClick = function()

          -- Make sure the script is there.
          local actionScript = selectedScript:FindFirstChild(`{actionType}ActionScript`);
          if not actionScript then

            local newActionScript = root.Templates:FindFirstChild(`Dialogue{actionType}ActionScriptTemplate`):Clone();
            newActionScript.Name = `{actionType}ActionScript`;
            newActionScript.Parent = selectedScript;
            actionScript = newActionScript;

          end;

          assert(actionScript and actionScript:IsA("ModuleScript"), `{actionType} action script not found for {selectedScript:GetFullName()}.`);

          plugin:OpenScript(actionScript);

        end;
      });

      table.insert(actionsDropdownOptions, option);

    end;

    local actionsDropdown = React.createElement(Dropdown, {
      layoutOrder = #dialogueOptions + 1;
      key = "ActionsDropdown";
      text = "Edit actions";
      size = UDim2.fromOffset(125, 30);
      isOpen = isActionsDropdownOpen;
      setIsOpen = setIsActionsDropdownOpen;
    }, actionsDropdownOptions);

    table.insert(dialogueOptions, actionsDropdown);

  end;

  local deleteButton = React.createElement(Button, {
    key = "DeleteButton";
    text = "Delete";
    backgroundColor = colors.backgroundWarning;
    layoutOrder = #dialogueOptions + 1;
    onClick = function()

      local historyIdentifier = beginHistoryRecording(`Delete {selectedDialogueType:lower()} script`);

      local newSelection = if selectedScript.Parent then selectedScript.Parent.Parent else nil;
      Selection:Set({newSelection});
      selectedScript.Parent = nil;

      finishHistoryRecording(historyIdentifier);

    end;
  });

  table.insert(dialogueOptions, deleteButton);

  return React.createElement("Frame", {
    BackgroundTransparency = 1;
    LayoutOrder = layoutOrder;
    Size = UDim2.fromScale(1, 0);
    AutomaticSize = Enum.AutomaticSize.Y;
  }, {
    UIListLayout = React.createElement("UIListLayout", {
      SortOrder = Enum.SortOrder.LayoutOrder;
      FillDirection = Enum.FillDirection.Horizontal;
      Padding = UDim.new(0, 10);
      Wraps = true;
    });
    DialogueOptions = React.createElement(React.Fragment, {}, dialogueOptions);
  });

end;

return React.memo(DialogueOptions);


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local DynamicContentCheckbox = require(script.Parent);
local VirtualService = require(root.VirtualService);
local React = require(root.roblox_packages.react);
local ReactErrorBoundary = require(root.roblox_packages.ReactErrorBoundary);
local ErrorBoundary = ReactErrorBoundary.ErrorBoundary;
local ReactRoblox = require(root.roblox_packages["react-roblox"]);
local IJW = require(root.roblox_packages.ijw);
local expect = IJW.expect;
local describe = IJW.describe;
local it = IJW.it;
local VirtualSelection = require(root.DialogueEditor.mocks.Selection);
local VirtualChangeHistoryService = require(root.DialogueEditor.mocks.ChangeHistoryService);

local screenGui: ScreenGui?;
local reactRoot: ReactRoblox.RootType?;
local propagatedErrorMessage;
local eventConnection: RBXScriptConnection?;

return {

  describe("DynamicContentCheckbox", function()

    local function MockComponent(properties: any)

      local selectedScript = properties.selectedScript;
      local onErrored = properties.onErrored or function() end;
      local onRendered = properties.onRendered or function() end;

      React.useEffect(function()

        onRendered();

      end, {onRendered});

      return React.createElement(ErrorBoundary, {
        FallbackComponent = React.Fragment;
        onError = onErrored;
      }, {
        React.createElement(DynamicContentCheckbox, {
          selectedScript = selectedScript;
          layoutOrder = 1;
        })
      });

    end;

    local function verifyReactStatus()

      if propagatedErrorMessage then

        error(propagatedErrorMessage);

      end;

    end;

    local function createDialogueScript(type: "Conversation" | "Message" | "Redirect")

      local dialogueScript = Instance.new("ModuleScript");

      if type == "Conversation" then

        dialogueScript:AddTag("DialogueMakerConversationScript");

      else

        dialogueScript:AddTag("DialogueMakerDialogueScript");
        dialogueScript:SetAttribute("DialogueType", type);

      end;

      return dialogueScript;

    end;

    local function render(type: "Conversation" | "Message" | "Redirect"): ModuleScript

      assert(reactRoot, "React root should be initialized before running tests.");

      local selectedScript = createDialogueScript(type);
      local didRender = false;
      local element = React.createElement(MockComponent, {
        selectedScript = selectedScript;
        onErrored = function(errorMessage)

          propagatedErrorMessage = errorMessage;

        end;
        onRendered = function()

          didRender = true;

        end;
      });

      reactRoot:render(element);
      repeat task.wait() until didRender or propagatedErrorMessage;
      verifyReactStatus();

      return selectedScript;

    end;

    return {

      it("can create ContentScript if it doesn't exist", function()

        expect(function()

          local selectedScript = render("Message");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dynamicContentCheckbox = screenGui:FindFirstChildOfClass("Frame");
          assert(dynamicContentCheckbox, "DynamicContentCheckbox should be rendered.");

          local checkbox = dynamicContentCheckbox:FindFirstChild("Checkbox");
          assert(checkbox and checkbox:IsA("TextButton"), "Checkbox should be rendered inside DynamicContentCheckbox.");

          VirtualService.events.GuiButton.Activated:fireEvent(checkbox);
          selectedScript:WaitForChild("ContentScript");

        end).toFinishBeforeSeconds(1);

      end);

      it(`can toggle "IsDisabled" attribute on ContentScript`, function()

        expect(function()

          local selectedScript = render("Message");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dynamicContentCheckbox = screenGui:FindFirstChildOfClass("Frame");
          assert(dynamicContentCheckbox, "DynamicContentCheckbox should be rendered.");

          local checkbox = dynamicContentCheckbox:FindFirstChild("Checkbox");
          assert(checkbox and checkbox:IsA("TextButton"), "Checkbox should be rendered inside DynamicContentCheckbox.");

          VirtualService.events.GuiButton.Activated:fireEvent(checkbox);
          local contentScript = selectedScript:WaitForChild("ContentScript");
          verifyReactStatus();
          checkbox:GetAttributeChangedSignal("IsChecked"):Wait();
          expect(contentScript:GetAttribute("IsDisabled")).toBe(false);

          VirtualService.events.GuiButton.Activated:fireEvent(checkbox);
          checkbox:GetAttributeChangedSignal("IsChecked"):Wait();
          verifyReactStatus();
          expect(contentScript:GetAttribute("IsDisabled")).toBe(true);

          VirtualService.events.GuiButton.Activated:fireEvent(checkbox);
          checkbox:GetAttributeChangedSignal("IsChecked"):Wait();
          verifyReactStatus();
          expect(contentScript:GetAttribute("IsDisabled")).toBe(false);

        end).toFinishBeforeSeconds(1);

      end);

      it(`automatically refreshes if "IsDisabled" attribute is toggled`, function()

        expect(function()

          local selectedScript = render("Message");

          assert(screenGui, "ScreenGui should be initialized before running tests.");
          local dynamicContentCheckbox = screenGui:FindFirstChildOfClass("Frame");
          assert(dynamicContentCheckbox, "DynamicContentCheckbox should be rendered.");

          local checkbox = dynamicContentCheckbox:FindFirstChild("Checkbox");
          assert(checkbox and checkbox:IsA("TextButton"), "Checkbox should be rendered inside DynamicContentCheckbox.");

          -- Create the ContentScript
          VirtualService.events.GuiButton.Activated:fireEvent(checkbox);
          local contentScript = selectedScript:WaitForChild("ContentScript");
          verifyReactStatus();

          -- Initially, the ContentScript should not be disabled, so let's disable it.
          checkbox:GetAttributeChangedSignal("IsChecked"):Wait();
          contentScript:SetAttribute("IsDisabled", true);
          checkbox:GetAttributeChangedSignal("IsChecked"):Wait();
          expect(checkbox:GetAttribute("IsChecked")).toBe(false);

          -- Now, let's enable it again.
          contentScript:SetAttribute("IsDisabled", false);
          checkbox:GetAttributeChangedSignal("IsChecked"):Wait();
          expect(checkbox:GetAttribute("IsChecked")).toBe(true);

        end).toFinishBeforeSeconds(1);

      end);

    }

  end, {
    beforeEach = function()

      local newScreenGui = Instance.new("ScreenGui");
      screenGui = newScreenGui;
      reactRoot = ReactRoblox.createRoot(newScreenGui);

      local virtualSelection = VirtualSelection.new();
      local virtualChangeHistoryService = VirtualChangeHistoryService.new();
      VirtualService.globals.game = {
        GetService = function(self, serviceName: string): unknown

          if serviceName == "Selection" then

            return virtualSelection;

          elseif serviceName == "ChangeHistoryService" then

            return virtualChangeHistoryService;

          end;

          return game:GetService(serviceName);

        end;
      };

    end;
    afterEach = function()

      if reactRoot then

        reactRoot:unmount();
        reactRoot = nil;

      end;

      if screenGui then

        screenGui:Destroy();
        screenGui = nil;

      end;

      if eventConnection then

        eventConnection:Disconnect();
        eventConnection = nil;

      end;

      propagatedErrorMessage = nil;
      VirtualService.globals.game = game;

    end;
  })
};


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local Checkbox = require(root.DialogueEditor.components.Checkbox);
local useChangeHistory = require(root.DialogueEditor.hooks.useChangeHistory);
local useDialogueContentScript = require(script.Parent.Parent.hooks.useDialogueContentScript);

export type DynamicContentCheckboxProperties = {
  selectedScript: ModuleScript;
  layoutOrder: number;
}

local function DynamicContentCheckbox(properties: DynamicContentCheckboxProperties)

  local selectedScript = properties.selectedScript;
  local dialogueContentScript, isDialogueContentScriptEnabled = useDialogueContentScript(selectedScript);
  local layoutOrder = properties.layoutOrder;
  local beginHistoryRecording, finishHistoryRecording = useChangeHistory();

  return React.createElement(Checkbox, {
    text = "Dynamic content";
    isChecked = isDialogueContentScriptEnabled;
    layoutOrder = layoutOrder;
    onChanged = function(isChecked: boolean)

      local historyIdentifier = beginHistoryRecording(`{if isChecked then "Disable" else "Enable"} dialogue dynamic content`);

      if not dialogueContentScript then

        local newDialogueContentScript = root.Templates.DialogueContentScriptTemplate:Clone();
        newDialogueContentScript.Name = "ContentScript";
        newDialogueContentScript.Parent = selectedScript;
        dialogueContentScript = newDialogueContentScript;

      end;

      assert(dialogueContentScript and dialogueContentScript:IsA("ModuleScript"), `Dialogue content script not found for {selectedScript:GetFullName()}.`);

      dialogueContentScript:SetAttribute("IsDisabled", not isChecked);

      finishHistoryRecording(historyIdentifier);

    end;
  });

end;

return React.memo(DynamicContentCheckbox);


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local ContentPreview = require(script.Parent.ContentPreview);
local Paragraph = require(root.DialogueEditor.components.Paragraph);
local InstanceInput = require(root.DialogueEditor.components.InstanceInput);
local useChangeHistory = require(root.DialogueEditor.hooks.useChangeHistory);

export type RedirectSelectorProperties = {
  selectedScript: ModuleScript;
  layoutOrder: number;
}

local function RedirectSelector(properties: RedirectSelectorProperties)

  local selectedScript = properties.selectedScript;
  local layoutOrder = properties.layoutOrder;
  local beginHistoryRecording, finishHistoryRecording = useChangeHistory();

  local getDestinationScript = React.useCallback(function(): ModuleScript?

    local destinationScript = selectedScript:FindFirstChild("RedirectValue");
    if not destinationScript or not destinationScript:IsA("ObjectValue") or not destinationScript.Value or not destinationScript.Value:IsA("ModuleScript") then

      return;

    end

    return destinationScript.Value;

  end, {selectedScript});

  local destinationScript: ModuleScript?, setDestinationScript = React.useState(getDestinationScript());

  React.useEffect(function()

    local function refreshDestinationScript()

      setDestinationScript(getDestinationScript());

    end;

    refreshDestinationScript();
    local childAddedConnection = selectedScript.ChildAdded:Connect(refreshDestinationScript);
    local childRemovedConnection = selectedScript.ChildRemoved:Connect(refreshDestinationScript);

    return function()

      childAddedConnection:Disconnect();
      childRemovedConnection:Disconnect();

    end;

  end, {selectedScript :: unknown, getDestinationScript});

  local destinationDialogueType: string? = if destinationScript then destinationScript:GetAttribute("DialogueType") :: string? else nil;
  if typeof(destinationDialogueType) ~= "string" then

    destinationDialogueType = nil;

  end;

  return React.createElement(ContentPreview, {
    layoutOrder = layoutOrder;
  }, {
    NotificationLabel = React.createElement(Paragraph, {
      text = `The conversation should continue from the following {(destinationDialogueType or "dialogue"):lower()}:`;
      layoutOrder = 1;
    });
    NotificationButton = React.createElement(InstanceInput, {
      layoutOrder = 2;
      value = destinationScript :: Instance?;
      className = "ModuleScript";
      onChanged = function(instance)

        if instance and not instance:HasTag("DialogueMakerDialogueScript") then

          return;

        end;

        local historyIdentifier = beginHistoryRecording("Set redirect value");

        local destinationScriptValue = selectedScript:FindFirstChild("RedirectValue");
        if not destinationScriptValue then

          local newDestinationScriptValue = Instance.new("ObjectValue");
          newDestinationScriptValue.Name = "RedirectValue";
          newDestinationScriptValue.Parent = selectedScript;
          destinationScriptValue = newDestinationScriptValue;

        end;

        assert(destinationScriptValue and destinationScriptValue:IsA("ObjectValue"), "Expected RedirectValue to be an ObjectValue");

        destinationScriptValue.Value = instance;

        finishHistoryRecording(historyIdentifier);

        setDestinationScript(getDestinationScript());

      end;
    });
  });

end;

return React.memo(RedirectSelector);


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local useChangeHistory = require(root.DialogueEditor.hooks.useChangeHistory);
local useStudioColors = require(root.DialogueEditor.hooks.useStudioColors);

export type StaticContentEditorProperties = {
  selectedScript: ModuleScript;
  layoutOrder: number;
}

local function StaticContentEditor(properties: StaticContentEditorProperties)

  local selectedScript = properties.selectedScript;
  local layoutOrder = properties.layoutOrder;
  local colors = useStudioColors();
  local beginHistoryRecording, finishHistoryRecording = useChangeHistory();

  local getDialogueContent = React.useCallback(function(): string

    local dialogueContent = selectedScript:GetAttribute("DialogueContent");

    if typeof(dialogueContent) ~= "string" then

      return "";

    end

    return dialogueContent;

  end, {selectedScript});

  local dialogueContent, setDialogueContent = React.useState(getDialogueContent());

  React.useEffect(function()

    local function refreshDialogueContent()

      setDialogueContent(getDialogueContent());

    end;

    refreshDialogueContent();
    local attributeChangedConnection = selectedScript:GetAttributeChangedSignal("DialogueContent"):Connect(refreshDialogueContent);

    return function()

      attributeChangedConnection:Disconnect();

    end;

  end, {selectedScript});

  return React.createElement("TextBox", {
    Text = dialogueContent;
    PlaceholderText = "Enter dialogue content here. For variables and effects, use dynamic content.";
    TextColor3 = colors.text;
    ClearTextOnFocus = false;
    TextSize = 14;
    LayoutOrder = layoutOrder;
    Size = UDim2.new(1, 0, 0, 100);
    BackgroundColor3 = colors.input;
    FontFace = Font.fromName("BuilderSans", Enum.FontWeight.Regular);
    TextXAlignment = Enum.TextXAlignment.Left;
    TextYAlignment = Enum.TextYAlignment.Top;
    TextWrapped = true;
    [React.Event.FocusLost] = function(self: TextBox)

      local historyIdentifier = beginHistoryRecording("Set static dialogue content");

      selectedScript:SetAttribute("DialogueContent", self.Text);

      finishHistoryRecording(historyIdentifier);

    end;
  }, {
    UIPadding = React.createElement("UIPadding", {
      PaddingTop = UDim.new(0, 15);
      PaddingBottom = UDim.new(0, 15);
      PaddingLeft = UDim.new(0, 15);
      PaddingRight = UDim.new(0, 15);
    });
    UISizeConstraint = React.createElement("UISizeConstraint", {
      MinSize = Vector2.new(0, 30);
    });
    UICorner = React.createElement("UICorner", {
      CornerRadius = UDim.new(0, 5);
    });
  });

end;

return React.memo(StaticContentEditor);


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);

local function useDialogueContentScript(dialogueScript: ModuleScript): (ModuleScript?, boolean)

  local getDialogueContentScript = React.useCallback(function(): ModuleScript?

    local contentScript = dialogueScript:FindFirstChild("ContentScript");
    if contentScript and contentScript:IsA("ModuleScript") then

      return contentScript;

    end

    return;

  end, { dialogueScript });

  local dialogueContentScript: ModuleScript?, setDialogueContentScript = React.useState(getDialogueContentScript());
  local isEnabled, setIsEnabled = React.useState(if dialogueContentScript then not dialogueContentScript:GetAttribute("IsDisabled") else false);

  React.useEffect(function()

    local function updateDialogueContentScript()

      setDialogueContentScript(getDialogueContentScript());

    end

    local childAddedConnection = dialogueScript.ChildAdded:Connect(updateDialogueContentScript);
    local childRemovedConnection = dialogueScript.ChildRemoved:Connect(updateDialogueContentScript);
    task.spawn(updateDialogueContentScript);

    return function()

      childAddedConnection:Disconnect();
      childRemovedConnection:Disconnect();

    end;

  end, { dialogueScript });

  React.useEffect(function()

    if not dialogueContentScript then

      setIsEnabled(false);
      return;

    end;

    local attributeChangedConnection = dialogueContentScript:GetAttributeChangedSignal("IsDisabled"):Connect(function()

      setIsEnabled(not dialogueContentScript:GetAttribute("IsDisabled"));

    end);

    setIsEnabled(not dialogueContentScript:GetAttribute("IsDisabled"));

    return function()

      attributeChangedConnection:Disconnect();

    end;

  end, { dialogueContentScript });

  return dialogueContentScript, isEnabled;

end;

return useDialogueContentScript;


--!strict

local TweenService = game:GetService("TweenService");

local root = script.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local DialogueGroupContainer = require(script.components.DialogueGroupContainer);
local Preview = require(script.components.Preview);
local useStudioColors = require(root.DialogueEditor.hooks.useStudioColors);
local useDialogueScriptType = require(root.DialogueEditor.hooks.useDialogueScriptType);
local useDialogueMakerScriptSelection = require(root.DialogueEditor.hooks.useDialogueMakerScriptSelection);

type DialogueScriptType = useDialogueScriptType.DialogueScriptType;

export type DialogueTableProperties = {
  plugin: Plugin;
  setSettingsTarget: (target: ModuleScript?) -> ();
  layoutOrder: number;
}

local function Explorer(props: DialogueTableProperties)

  local plugin = props.plugin;
  local selectedScript = useDialogueMakerScriptSelection();
  local setSettingsTarget = props.setSettingsTarget;
  local layoutOrder = props.layoutOrder;
  local colors = useStudioColors();
  local dialogueScriptType: DialogueScriptType? = useDialogueScriptType(selectedScript) :: DialogueScriptType?;
  local ref = React.useRef(nil :: ScrollingFrame?);

  React.useEffect(function()

    if not ref.current then

      return;

    end;

    ref.current.CanvasPosition = Vector2.new(0, 0);

    local tween: Tween?;
    local tweenTask: thread?;
    local function resetTween()

      if tweenTask then

        task.cancel(tweenTask);
        tweenTask = nil;

      end;

      if tween then

        tween:Cancel();
        tween = nil;

      end;

      ref.current.ScrollBarImageTransparency = 0;

      tweenTask = task.delay(3, function()

        local newTween = TweenService:Create(ref.current, TweenInfo.new(), {
          ScrollBarImageTransparency = 0.55;
        });
        tween = newTween;

        newTween:Play();

      end);

    end;

    local scrollingPositionConnection = ref.current:GetPropertyChangedSignal("CanvasPosition"):Connect(resetTween);
    resetTween();

    return function()

      if tween then

        tween:Cancel();
        tween = nil;

      end;

      if tweenTask then

        task.cancel(tweenTask);
        tweenTask = nil;

      end;

      scrollingPositionConnection:Disconnect();

    end;

  end, {selectedScript});

  return React.createElement("ScrollingFrame", {
    Size = UDim2.fromScale(1, 1);
    BackgroundTransparency = 1;
    LayoutOrder = layoutOrder;
    AutomaticCanvasSize = Enum.AutomaticSize.Y;
    CanvasSize = UDim2.fromScale(1, 0);
    ScrollingDirection = Enum.ScrollingDirection.Y;
    BorderSizePixel = 0;
    ScrollBarImageColor3 = colors.toolbar;
    ref = ref;
  }, {
    UIListLayout = React.createElement("UIListLayout", {
      HorizontalAlignment = Enum.HorizontalAlignment.Center;
    });
    UIFlexItem = React.createElement("UIFlexItem", {
      FlexMode = Enum.UIFlexMode.Shrink;
    });
    Content = React.createElement("Frame", {
      Size = UDim2.fromScale(1, 1);
      AutomaticSize = Enum.AutomaticSize.Y;
      BackgroundTransparency = 1;
    }, {
      UISizeConstraint = React.createElement("UISizeConstraint", {
        MaxSize = Vector2.new(750, math.huge);
      });
      UIPadding = React.createElement("UIPadding", {
        PaddingLeft = UDim.new(0, 15);
        PaddingRight = UDim.new(0, 15);
        PaddingTop = UDim.new(0, 15);
        PaddingBottom = UDim.new(0, 15);
      });
      UIListLayout = React.createElement("UIListLayout", {
        SortOrder = Enum.SortOrder.LayoutOrder;
        Padding = UDim.new(0, 15);
        HorizontalAlignment = Enum.HorizontalAlignment.Center;
      });
      Preview = if selectedScript then
        React.createElement(Preview, {
          layoutOrder = 1;
          selectedScript = selectedScript;
          dialogueScriptType = dialogueScriptType;
          plugin = plugin;
        })
      else nil;
      DialogueGroupContainer = if dialogueScriptType ~= "Redirect" then
        React.createElement(DialogueGroupContainer, {
          selectedScript = selectedScript;
          layoutOrder = 2;
          plugin = plugin;
          setSettingsTarget = setSettingsTarget;
        })
      else nil;
    });
  })

end;

return React.memo(Explorer);


--!strict

local ReplicatedStorage = game:GetService("ReplicatedStorage");

local root = script.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local Paragraph = require(root.DialogueEditor.components.Paragraph);
local Button = require(root.DialogueEditor.components.Button);
local InstanceInput = require(root.DialogueEditor.components.InstanceInput);
local useChangeHistory = require(root.DialogueEditor.hooks.useChangeHistory);

local function InitialSetupScreen()

  local beginHistoryRecording, finishHistoryRecording = useChangeHistory();

  return React.createElement("Frame", {
    Size = UDim2.new(1, 0, 1, 0);
    BackgroundTransparency = 1;
  }, {
    UIPadding = React.createElement("UIPadding", {
      PaddingTop = UDim.new(0, 15);
      PaddingBottom = UDim.new(0, 15);
      PaddingLeft = UDim.new(0, 15);
      PaddingRight = UDim.new(0, 15);
    });
    UIListLayout = React.createElement("UIListLayout", {
      SortOrder = Enum.SortOrder.LayoutOrder;
      Padding = UDim.new(0, 15);
      VerticalAlignment = Enum.VerticalAlignment.Center;
      HorizontalAlignment = Enum.HorizontalAlignment.Center;
    });
    Heading = React.createElement(Paragraph, {
      text = "Welcome to the Dialogue Maker plugin!";
      layoutOrder = 1;
      textSize = 24;
    });
    Description = React.createElement(Paragraph, {
      text = "This is a tool to help you create and manage dialogue in your Roblox games.";
      layoutOrder = 2;
    });
    Instructions = React.createElement(Paragraph, {
      text = "To get started, please add the Dialogue Maker Kit to your game, or select your existing packages folder that was created by your package manager (Wally, pesde, etc.).";
      layoutOrder = 3;
    });
    Options = React.createElement("Frame", {
      BackgroundTransparency = 1;
      LayoutOrder = 4;
      AutomaticSize = Enum.AutomaticSize.Y;
    }, {
      UIListLayout = React.createElement("UIListLayout", {
        SortOrder = Enum.SortOrder.LayoutOrder;
        FillDirection = Enum.FillDirection.Vertical;
        Padding = UDim.new(0, 15);
        VerticalAlignment = Enum.VerticalAlignment.Center;
        HorizontalAlignment = Enum.HorizontalAlignment.Center;
      });
      AddDialogueMakerKitButton = React.createElement(Button, {
        text = "Add Dialogue Maker Kit";
        layoutOrder = 1;
        onClick = function()

          local historyIdentifier = beginHistoryRecording("Add Dialogue Maker Kit");

          local dialogueMakerKit = root.DialogueMakerKit:Clone();
          dialogueMakerKit:AddTag("DialogueMakerKit");
          dialogueMakerKit.Parent = ReplicatedStorage;

          finishHistoryRecording(historyIdentifier);

          print(`[Dialogue Maker] Added Dialogue Maker Kit to ReplicatedStorage. Feel free to move it if it doesn't work there. If you use Rojo, you may need to add the folder to your project using "Save to File".`);

        end;
      });
      SelectPackagesFolderButton = React.createElement(InstanceInput, {
        className = "Folder";
        layoutOrder = 2;
        onChanged = function(folder)

          local historyIdentifier = beginHistoryRecording("Select Dialogue Maker plugin packages folder");

          if not folder then

            return;

          end;

          folder:AddTag("DialogueMakerPackages");

          finishHistoryRecording(historyIdentifier);

        end;
      });
    });
  });

end;

return React.memo(InitialSetupScreen);


--!strict

local Selection = game:GetService("Selection");

local root = script.Parent.Parent.Parent;
local Button = require(root.DialogueEditor.components.Button);
local React = require(root.roblox_packages.react);

export type InstanceInputProperties = {
  value: Instance?;
  defaultValue: Instance?;
  className: string?;
  layoutOrder: number;
  onChanged: (value: Instance?) -> ();
}

local function InstanceInput<InstanceType>(properties: InstanceInputProperties)

  local onChanged = properties.onChanged;
  local layoutOrder = properties.layoutOrder or 1;

  local className = properties.className or "Instance";
  local isSelecting, setIsSelecting = React.useState(false);

  React.useEffect(function(): ()

    if isSelecting then

      local previousSelection = Selection:Get()[1];

      local selectionChangedConnection = Selection.SelectionChanged:Once(function()

        local selectedInstance = Selection:Get()[1];

        if previousSelection then

          Selection:Set({previousSelection});

        end;

        setIsSelecting(false);

        if selectedInstance and (not properties.className or selectedInstance:IsA(properties.className)) then

          onChanged(selectedInstance);

        else

          onChanged(nil);

        end;

      end);

      return function()

        selectionChangedConnection:Disconnect();

      end;

    end;

  end, {isSelecting});

  return React.createElement(Button, {
    layoutOrder = layoutOrder;
    text = if isSelecting then `Selecting {className}...` else (if properties.value then properties.value.Name elseif properties.defaultValue then properties.defaultValue.Name else nil) or `Select {className}`;
    onClick = function()

      setIsSelecting(not isSelecting);

    end;
  });

end;

return React.memo(InstanceInput);


--!strict

local root = script.Parent.Parent.Parent;
local React = require(root.roblox_packages.react);
local useStudioColors = require(root.DialogueEditor.hooks.useStudioColors);

export type ParagraphProperties = {
  text: string;
  textColor3: Color3?;
  automaticSize: Enum.AutomaticSize?;
  textWrapped: boolean?;
  layoutOrder: number?;
  textSize: number?;
  children: React.ReactNode?;
  fontWeight: Enum.FontWeight?;
}

local function Paragraph(properties: ParagraphProperties)

  local colors = useStudioColors();
  local text = properties.text;
  local automaticSize = if properties.automaticSize ~= nil then properties.automaticSize else Enum.AutomaticSize.XY;
  local textWrapped = if properties.textWrapped ~= nil then properties.textWrapped else true;
  local layoutOrder = if properties.layoutOrder ~= nil then properties.layoutOrder else 1;
  local textColor3 = if properties.textColor3 ~= nil then properties.textColor3 else colors.text;
  local textSize = if properties.textSize ~= nil then properties.textSize else 14;
  local children = properties.children;
  local fontWeight = if properties.fontWeight ~= nil then properties.fontWeight else Enum.FontWeight.Regular;

  return React.createElement("TextLabel", {
    AutomaticSize = automaticSize;
    Text = text;
    TextColor3 = textColor3;
    TextWrapped = textWrapped;
    FontFace = Font.fromName("BuilderSans", fontWeight);
    TextSize = textSize;
    BackgroundTransparency = 1;
    LayoutOrder = layoutOrder;
  }, children);

end;

return React.memo(Paragraph);


--!strict

local root = script.Parent.Parent.Parent.Parent.Parent.Parent.Parent;
local packages = root.roblox_packages;
local React = require(packages.react);
local InstanceInput = require(root.DialogueEditor.components.InstanceInput);
local ToggleInput = require(root.DialogueEditor.components.ToggleInput);
local TextInput = require(root.DialogueEditor.components.TextInput);
local useStudioColors = require(root.DialogueEditor.hooks.useStudioColors);
local useStudioIcons = require(root.DialogueEditor.hooks.useStudioIcons);
local settingsMetadata = require(script.Parent.Parent.Parent.Parent.settingsMetadata);
local useDialogueMakerPackages = require(root.DialogueEditor.hooks.useDialogueMakerPackages);

type SettingMetadata = settingsMetadata.SettingMetadata;

export type SettingProperties = {
  settingMetadata: SettingMetadata;
  settingsTarget: ModuleScript;
  value: any?;
  onChanged: (value: any) -> ();
  onReset: () -> ();
  layoutOrder: number;
}

local function Setting(properties: SettingProperties)

  local settingMetadata = properties.settingMetadata;
  local settingsTarget = properties.settingsTarget;
  local colors = useStudioColors();
  local icons = useStudioIcons();
  local dialogueMakerPackages = useDialogueMakerPackages();
  local defaultValue = if settingsTarget:HasTag("DialogueMakerLoader") and settingMetadata.name == "componentScript" and dialogueMakerPackages then dialogueMakerPackages:FindFirstChild("StandardTheme") else settingMetadata.defaultValue;

  local inputValue = React.useMemo(function(): React.ReactElement

    if settingMetadata.type == "boolean" then

      local isEnabled = if properties.value ~= nil then properties.value else defaultValue;

      return React.createElement(ToggleInput, {
        isEnabled = isEnabled;
        layoutOrder = 1;
        onChanged = function(value: boolean): ()

          properties.onChanged(value);

        end;
      });

    elseif settingMetadata.type == "string" or settingMetadata.type == "number" then

      return React.createElement(TextInput, {
        value = properties.value;
        placeholder = settingMetadata.defaultValue;
        layoutOrder = 1;
        onChanged = function(value: string): ()

          properties.onChanged(if value == "" then nil elseif settingMetadata.type == "number" then tonumber(value) else value);

        end;
      });

    elseif settingMetadata.type == "Instance" then

      return React.createElement(InstanceInput, {
        value = properties.value;
        layoutOrder = 1;
        defaultValue = defaultValue;
        className = settingMetadata["className"];
        onChanged = function(value: Instance?): ()

          properties.onChanged(value);

        end;
      });

    else

      error(`Unsupported setting type: {settingMetadata.type}`);

    end;

  end, {settingMetadata.type :: unknown, properties.value, settingMetadata.defaultValue, properties.onChanged});

  return React.createElement("Frame", {
    BackgroundTransparency = 0;
    BackgroundColor3 = colors.toolbar;
    AutomaticSize = Enum.AutomaticSize.Y;
    Size = UDim2.fromScale(1, 0);
    LayoutOrder = properties.layoutOrder;
    BorderSizePixel = 0;
  }, {
    UIStroke = React.createElement("UIStroke", {
      Color = colors.border;
      Thickness = 1;
      ApplyStrokeMode = Enum.ApplyStrokeMode.Border;
    });
    UIPadding = React.createElement("UIPadding", {
      PaddingTop = UDim.new(0, 10);
      PaddingBottom = UDim.new(0, 10);
      PaddingLeft = UDim.new(0, 10);
      PaddingRight = UDim.new(0, 10);
    });
    UIListLayout = React.createElement("UIListLayout", {
      SortOrder = Enum.SortOrder.LayoutOrder;
      FillDirection = Enum.FillDirection.Horizontal;
      HorizontalFlex = Enum.UIFlexAlignment.SpaceBetween;
      VerticalAlignment = Enum.VerticalAlignment.Center;
      Padding = UDim.new(0, 15);
    });
    UICorner = React.createElement("UICorner", {
      CornerRadius = UDim.new(0, 5);
    });
    Information = React.createElement("Frame", {
      Size = UDim2.fromScale(1, 0);
      AutomaticSize = Enum.AutomaticSize.Y;
      LayoutOrder = 1;
      BackgroundTransparency = 1;
    }, {
      UIFlexItem = React.createElement("UIFlexItem", {
        FlexMode = Enum.UIFlexMode.Fill;
      }),
      UIListLayout = React.createElement("UIListLayout", {
        SortOrder = Enum.SortOrder.LayoutOrder;
      });
      NameLabel = React.createElement("TextLabel", {
        AutomaticSize = Enum.AutomaticSize.XY;
        LayoutOrder = 1;
        Text = settingMetadata.label;
        TextSize = 14;
        FontFace = Font.fromName("BuilderSans", Enum.FontWeight.Bold);
        TextColor3 = colors.text;
        BackgroundTransparency = 1;
        TextWrapped = true;
        TextXAlignment = Enum.TextXAlignment.Left;
      });
      DescriptionLabel = React.createElement("TextLabel", {
        LayoutOrder = 2;
        AutomaticSize = Enum.AutomaticSize.XY;
        Text = settingMetadata.description;
        TextSize = 12;
        FontFace = Font.fromName("BuilderSans", Enum.FontWeight.Regular);
        TextColor3 = colors.text;
        TextTransparency = 0.1;
        BackgroundTransparency = 1;
        TextWrapped = true;
        TextXAlignment = Enum.TextXAlignment.Left;
      }),
    });
    RightGroup = React.createElement("Frame", {
      AutomaticSize = Enum.AutomaticSize.XY;
      LayoutOrder = 2;
      BackgroundTransparency = 1;
    }, {
      UIListLayout = React.createElement("UIListLayout", {
        SortOrder = Enum.SortOrder.LayoutOrder;
        FillDirection = Enum.FillDirection.Horizontal;
        VerticalAlignment = Enum.VerticalAlignment.Center;
        HorizontalAlignment = Enum.HorizontalAlignment.Right;
        Padding = UDim.new(0, 5);
      });
      InputValue = inputValue;
      ResetButton = React.createElement("ImageButton", {
        LayoutOrder = 2;
        BackgroundTransparency = 1;
        Size = UDim2.fromOffset(20, 20);
        ImageTransparency = if properties.value == nil then 0.75 else 0;
        Image = icons.close;
        [React.Event.Activated] = function()

          properties.onReset();

        end;
      });
    })
  });

end;

return React.memo(Setting);