(function($, TD){
  if ($ === null){
    console.error("Missing jQuery");
  }
  
  if (!("$TD" in window)){
    console.error("Missing $TD");
  }
  
  if (!("$TDX" in window)){
    console.error("Missing $TDX");
  }
  
  //
  // Variable: Array of functions called after the website app is loaded.
  //
  let onAppReady = [];
  
  //
  // Variable: DOM object containing the main app element.
  //
  const app = typeof $ === "function" && $(document.body).children(".js-app");
  
  //
  // Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
  //
  const prependToFunction = function(func, extension){
    return function(){
      return extension.apply(this, arguments) === true ? undefined : func.apply(this, arguments);
    };
  };
  
  //
  // Function: Appends code at the end of a function.
  //
  const appendToFunction = function(func, extension){
    return function(){
      const res = func.apply(this, arguments);
      extension.apply(this, arguments);
      return res;
    };
  };
  
  //
  // Function: Triggers an internal debug crash when something is missing.
  //
  const crashDebug = function(message){
    console.error(message);
    debugger;
    
    if ("$TD" in window){
      $TD.crashDebug(message);
    }
  }
  
  //
  // Function: Throws if an object does not have a specified property.
  //
  const ensurePropertyExists = function(obj, ...chain){
    for(let index = 0; index < chain.length; index++){
      if (!obj.hasOwnProperty(chain[index])){
        throw "Missing property " + chain[index] + " in chain [obj]." + chain.join(".");
      }
      
      obj = obj[chain[index]];
    }
  };
  
  //
  // Function: Returns true if an object has a specified property, otherwise returns false with a debug-only error message.
  //
  const checkPropertyExists = function(obj, ...chain){
    try{
      ensurePropertyExists(obj, ...chain);
      return true;
    }catch(err){
      crashDebug(err);
      return false;
    }
  };
  
  //
  // Function: Throws if an element does not have a registered jQuery event.
  //
  const ensureEventExists = function(element, eventName){
    if (!(eventName in $._data(element, "events"))){
      throw "Missing jQuery event " + eventName + " in " + element.cloneNode().outerHTML;
    }
  };
  
  //
  // Function: Returns a jQuery object but also shows a debug-only error message if no elements are found.
  //
  const $$ = function(selector, context){
    const result = $(selector, context);
    
    if (!result.length){
      crashDebug("No elements were found for selector " + selector);
    }
    
    return result;
  };
  
  //
  // Function: Executes a function inside a try-catch to stop it from crashing everything.
  //
  const execSafe = function(func, fail){
    try{
      func();
    }catch(err){
      crashDebug("Caught error in function " + func.name)
      fail && fail();
    }
  };
  
  //
  // Function: Returns an object containing data about the column below the cursor.
  //
  const getHoveredColumn = function(){
    const hovered = document.querySelectorAll(":hover");
    
    for(let index = hovered.length - 1; index >= 0; index--){
      const ele = hovered[index];
      
      if (ele.tagName === "SECTION" && ele.classList.contains("js-column")){
        const obj = TD.controller.columnManager.get(ele.getAttribute("data-column"));
        
        if (obj){
          return { ele, obj };
        }
      }
    }
    
    return null;
  };
  
  //
  // Function: Returns an object containing data about the tweet below the cursor.
  //
  const getHoveredTweet = function(){
    const hovered = document.querySelectorAll(":hover");
    
    for(let index = hovered.length - 1; index >= 0; index--){
      const ele = hovered[index];
      
      if (ele.tagName === "ARTICLE" && ele.classList.contains("js-stream-item") && ele.hasAttribute("data-account-key")){
        const column = getHoveredColumn();
        
        if (column){
          const wrap = column.obj.findChirp(ele.getAttribute("data-key"));
          const obj = column.obj.findChirp(ele.getAttribute("data-tweet-id")) || wrap;
          
          if (obj){
            return { ele, obj, wrap, column };
          }
        }
      }
    }
    
    return null;
  };
  
  //
  // Function: Retrieves a property of an element with a specified class.
  //
  const getClassStyleProperty = function(cls, property){
    const column = document.createElement("div");
    column.classList.add(cls);
    column.style.display = "none";
    
    document.body.appendChild(column);
    const value = window.getComputedStyle(column).getPropertyValue(property);
    document.body.removeChild(column);
    
    return value;
  };
  
  //
  // Block: Fix columns missing any identifiable attributes to allow individual styles.
  //
  execSafe(function setupColumnAttrIdentifiers(){
    $(document).on("uiColumnRendered", function(e, data){
      const icon = data.$column.find(".column-type-icon").first();
      return if icon.length !== 1;
      
      const name = Array.prototype.find.call(icon[0].classList, cls => cls.startsWith("icon-"));
      return if !name;
      
      data.$column.attr("data-td-icon", name);
      data.column._tduck_icon = name;
    });
  });
  
  //
  // Block: Add TweetDuck buttons to the settings menu.
  //
  onAppReady.push(function setupSettingsDropdown(){
    document.querySelector("[data-action='settings-menu']").addEventListener("click", function(){
      setTimeout(function(){
        const menu = document.querySelector(".js-dropdown-content ul");
        return if !menu;
        
        const dividers = menu.querySelectorAll(":scope > li.drp-h-divider");
        const target = dividers[dividers.length - 1];
        
        target.insertAdjacentHTML("beforebegin", '<li class="is-selectable" data-tweetduck><a href="#" data-action>TweetDuck</a></li>');
        
        const button = menu.querySelector("[data-tweetduck]");
        
        button.querySelector("a").addEventListener("click", function(){
          $TD.openContextMenu();
        });
        
        button.addEventListener("mouseenter", function(){
          button.classList.add("is-selected");
        });
        
        button.addEventListener("mouseleave", function(){
          button.classList.remove("is-selected");
        })
      }, 0);
    });
  });
  
  //
  // Block: Hook into settings object to detect when the settings change, and update html attributes and notification layout.
  //
  execSafe(function hookTweetDeckSettings(){
    ensurePropertyExists(TD, "settings", "getFontSize");
    ensurePropertyExists(TD, "settings", "setFontSize");
    ensurePropertyExists(TD, "settings", "getTheme");
    ensurePropertyExists(TD, "settings", "setTheme");
    
    const doc = document.documentElement;
    
    const refreshSettings = function(){
      const fontSizeName = TD.settings.getFontSize();
      const themeName = TD.settings.getTheme();
      
      const tags = [
        "<html " + Array.prototype.map.call(doc.attributes, ele => `${ele.name}="${ele.value}"`).join(" ") + "><head>"
      ];
      
      for(let ele of document.head.querySelectorAll("link[rel='stylesheet']:not([data-td-exclude-notification]),meta[charset]")){
        tags.push(ele.outerHTML);
      }
      
      tags.push("<style type='text/css'>body { background: " + getClassStyleProperty("column-panel", "background-color") + " !important }</style>");
      
      doc.setAttribute("data-td-font", fontSizeName);
      doc.setAttribute("data-td-theme", themeName);
      $TD.loadNotificationLayout(fontSizeName, tags.join(""));
    };
    
    TD.settings.setFontSize = appendToFunction(TD.settings.setFontSize, function(name){
      setTimeout(refreshSettings, 0);
    });
    
    TD.settings.setTheme = appendToFunction(TD.settings.setTheme, function(name){
      setTimeout(refreshSettings, 0);
    });
    
    onAppReady.push(refreshSettings);
  });
  
  //
  // Block: Setup CSS injections.
  //
  execSafe(function setupStyleInjection(){
    const createStyle = function(id, styles){
      const ele = document.createElement("style");
      ele.id = id;
      ele.innerText = styles;
      document.head.appendChild(ele);
    };
    
    window.TDGF_injectBrowserCSS = function(styles){
      if (!document.getElementById("tweetduck-browser-css")){
        createStyle("tweetduck-browser-css", styles);
      }
    };
    
    window.TDGF_reinjectCustomCSS = function(styles){
      const prev = document.getElementById("tweetduck-custom-css");
      
      if (prev){
        prev.remove();
      }
      
      if (styles && styles.length){
        createStyle("tweetduck-custom-css", styles);
      }
    };
  });
  
  //
  // Block: Setup custom sound notification.
  //
  window.TDGF_setSoundNotificationData = function(custom, volume){
    const audio = document.getElementById("update-sound");
    audio.volume = volume / 100;
    
    const sourceId = "tduck-custom-sound-source";
    let source = document.getElementById(sourceId);
    
    if (custom && !source){
      source = document.createElement("source");
      source.id = sourceId;
      source.src = "https://ton.twimg.com/tduck/updatesnd";
      audio.prepend(source);
    }
    else if (!custom && source){
      audio.removeChild(source);
    }
    
    audio.load();
  };
  
  //
  // Block: Hook into composer event.
  //
  execSafe(function hookComposerEvents(){
    $(document).on("uiDrawerActive uiRwebComposerOptOut", function(e, data){
      return if e.type === "uiDrawerActive" && data.activeDrawer !== "compose";
      
      setTimeout(function(){
        $(document).trigger("tduckOldComposerActive");
      }, 0);
    });
  });
  
  //
  // Block: Setup a top tier account bamboozle scheme.
  //
  execSafe(function setupAccountLoadHook(){
    const realDisplayName = "TweetDuck";
    const realAvatar = "https://ton.twimg.com/tduck/avatar";
    const accountId = "957608948189880320";
    
    if (checkPropertyExists(TD, "services", "TwitterUser", "prototype", "fromJSONObject")){
      const prevFunc = TD.services.TwitterUser.prototype.fromJSONObject;
      
      TD.services.TwitterUser.prototype.fromJSONObject = function(){
        const obj = prevFunc.apply(this, arguments);
        
        if (obj.id === accountId){
          obj.name = realDisplayName;
          obj.emojifiedName = realDisplayName;
          obj.profileImageURL = realAvatar;
          obj.url = "https://tweetduck.chylex.com";
          
          if (obj.entities && obj.entities.url){
            obj.entities.url.urls = [{
              url: obj.url,
              expanded_url: obj.url,
              display_url: "tweetduck.chylex.com",
              indices: [ 0, 23 ]
            }];
          }
        }
        
        return obj;
      };
    }
    
    if (checkPropertyExists(TD, "services", "TwitterClient", "prototype", "typeaheadSearch")){
      const prevFunc = TD.services.TwitterClient.prototype.typeaheadSearch;
      
      TD.services.TwitterClient.prototype.typeaheadSearch = function(data, onSuccess, onError){
        if (data.query && data.query.toLowerCase().endsWith("tweetduck")){
          data.query = "TryMyAwesomeApp";
        }
        
        return prevFunc.call(this, data, function(result){
          for(let user of result.users){
            if (user.id_str === accountId){
              user.name = realDisplayName;
              user.profile_image_url = realAvatar;
              user.profile_image_url_https = realAvatar;
              break;
            }
          }
          
          onSuccess.apply(this, arguments);
        }, onError);
      };
    }
  });
  
  //
  // Block: Work around clipboard HTML formatting.
  //
  document.addEventListener("copy", function(){
    window.setTimeout($TD.fixClipboard, 0);
  });
  
  //
  // Block: Fix OS name and add ID to the document for priority CSS selectors.
  //
  (function(){
    const doc = document.documentElement;
    
    if (checkPropertyExists(TD, "util", "getOSName")){
      TD.util.getOSName = function(){
        return "windows";
      };
      
      doc.classList.remove("os-");
      doc.classList.add("os-windows");
    }
    
    doc.id = "tduck";
  })();
  
  //
  // Block: Disable TweetDeck metrics.
  //
  if (checkPropertyExists(TD, "metrics")){
    const noop = function(){};
    TD.metrics.inflate = noop;
    TD.metrics.inflateMetricTriple = noop;
    TD.metrics.log = noop;
    TD.metrics.makeKey = noop;
    TD.metrics.send = noop;
  }
  
  onAppReady.push(function disableMetrics(){
    const data = $._data(window);
    delete data.events["metric"];
    delete data.events["metricsFlush"];
  });
  
  //
  // Block: Import scripts.
  //
  #import "scripts/browser.globals.js"
  #import "scripts/browser.features.js"
  #import "scripts/browser.tweaks.js"
  
  //
  // Block: Register the TD.ready event, finish initialization, and load plugins.
  //
  $(document).one("TD.ready", function(){
    onAppReady.forEach(func => execSafe(func));
    onAppReady = null;
    
    if (window.TD_PLUGINS){
      window.TD_PLUGINS.onReady();
    }
  });

  //
  // Block: Ensure window.jQuery is available.
  //
  window.jQuery = $;
  
  //
  // Block: Skip the initial pre-login page.
  //
  if (checkPropertyExists(TD, "controller", "init", "showLogin")){
    TD.controller.init.showLogin = function(){
      location.href = "https://twitter.com/login?hide_message=true&redirect_after_login=https%3A%2F%2Ftweetdeck.twitter.com%2F%3Fvia_twitter_login%3Dtrue";
    };
  }
})(window.$ || null, window.TD || {});