Same question was asked for python here: Selenium - wait until element is present, visible and interactable. However the answers are not covering all the scenarios.
My implementation fails from time to time, when I am not in debug mode. I consider that waiting for an ID is such a trivial task that should have a straight forward KISS implementation and should never fail.
    public IWebElement WaitForId(string inputId, bool waitToInteract = false, int index = 0)
    {
        IWebElement uiElement = null;
        while (uiElement == null)
        {
            var items = WebDriver.FindElements(By.Id(inputId));
            var iteration = 0;
            while (items == null || items.Count < (index + 1) ||
                   (waitToInteract && (!items[index].Enabled || !items[index].Displayed)))
            {
                Thread.Sleep(500);
                items = WebDriver.FindElements(By.Id(inputId));
                if (items.Count == 0 && iteration == 10)
                {
                    // "still waiting...: " + inputId
                }
                if (iteration == 50)
                {
                    // "WaitForId not found: " + inputId
                    Debugger.Break(); // if tried too many times, maybe you want to investigate way
                }
                iteration++;
            }
            uiElement = WebDriver.FindElement(By.Id(inputId));
        }
        return uiElement;
    }
 
     
    