"use strict";
// License: MIT

import { ALLOWED_SCHEMES, TRANSFERABLE_PROPERTIES } from "./constants";
import { API } from "./api";
import { Finisher, makeUniqueItems } from "./item";
import { Prefs } from "./prefs";
import { _, locale } from "./i18n";
import { openPrefs, openManager } from "./windowutils";
import { filters } from "./filters";
import { getManager } from "./manager/man";
import {
  browserAction as action,
  menus as _menus, contextMenus as _cmenus,
  tabs,
  webNavigation as nav,
  // eslint-disable-next-line no-unused-vars
  Tab,
  // eslint-disable-next-line no-unused-vars
  MenuClickInfo,
  CHROME,
  runtime,
  history,
  sessions,
  // eslint-disable-next-line no-unused-vars
  OnInstalled,
} from "./browser";
import { Bus } from "./bus";
import { filterInSitu } from "./util";


const menus = typeof (_menus) !== "undefined" && _menus || _cmenus;

const GATHER = "/bundles/content-gather.js";

const CHROME_CONTEXTS = Object.freeze(new Set([
  "all",
  "audio",
  "browser_action",
  "editable",
  "frame",
  "image",
  "launcher",
  "link",
  "page",
  "page_action",
  "selection",
  "video",
]));

async function runContentJob(tab: Tab, file: string, msg: any) {
  try {
    if (tab && tab.incognito && msg) {
      msg.private = tab.incognito;
    }
    const res = await tabs.executeScript(tab.id, {
      file,
      allFrames: true,
      runAt: "document_start"
    });
    if (!msg) {
      return res;
    }
    const promises = [];
    const results: any[] = [];
    for (const frame of await nav.getAllFrames({ tabId: tab.id })) {
      promises.push(tabs.sendMessage(tab.id, msg, {
        frameId: frame.frameId}
      ).then(function(res: any) {
        results.push(res);
      }).catch(console.error));
    }
    await Promise.all(promises);
    return results;
  }
  catch (ex) {
    console.error("Failed to execute content script", file,
      ex.message || ex.toString(), ex);
    return [];
  }
}

type SelectionOptions = {
  selectionOnly: boolean;
  allTabs: boolean;
  turbo: boolean;
  tab: Tab;
};


class Handler {
  async processResults(turbo = false, results: any[]) {
    const links = this.makeUnique(results, "links");
    const media = this.makeUnique(results, "media");
    await API[turbo ? "turbo" : "regular"](links, media);
  }

  makeUnique(results: any[], what: string) {
    return makeUniqueItems(
      results.filter(e => e[what]).map(e => {
        const finisher = new Finisher(e);
        return filterInSitu(e[what].
          map((item: any) => finisher.finish(item)), e => !!e);
      }));
  }

  async performSelection(options: SelectionOptions) {
    try {
      const toptions: any = {
        currentWindow: true,
        discarded: false,
      };
      if (!CHROME) {
        toptions.hidden = false;
      }
      const selectedTabs = options.allTabs ?
        await tabs.query(toptions) as any[] :
        [options.tab];

      const textLinks = await Prefs.get("text-links", true);
      const goptions = {
        type: "DTA:gather",
        selectionOnly: options.selectionOnly,
        textLinks,
        schemes: Array.from(ALLOWED_SCHEMES.values()),
        transferable: TRANSFERABLE_PROPERTIES,
      };

      const results = await Promise.all(selectedTabs.
        map((tab: any) => runContentJob(tab, GATHER, goptions)));

      await this.processResults(options.turbo, results.flat());
    }
    catch (ex) {
      console.error(ex.toString(), ex.stack, ex);
    }
  }
}

function getMajor(version?: string) {
  if (!version) {
    return "";
  }
  const match = version.match(/^\d+\.\d+/);
  if (!match) {
    return "";
  }
  return match[0];
}

runtime.onInstalled.addListener(({reason, previousVersion}: OnInstalled) => {
  const {version} = runtime.getManifest();
  const major = getMajor(version);
  const prevMajor = getMajor(previousVersion);
  if (reason === "update" && major !== prevMajor) {
    tabs.create({
      url: `https://about.downthemall.org/changelog/?cur=${major}&prev=${prevMajor}`,
    });
  }
  else if (reason === "install") {
    tabs.create({
      url: `https://about.downthemall.org/4.0/?cur=${major}`,
    });
  }
});

locale.then(() => {
  const menuHandler = new class Menus extends Handler {
    constructor() {
      super();
      this.onClicked = this.onClicked.bind(this);
      const alls = new Map<string, string[]>();
      const mcreate = (options: any) => {
        if (CHROME) {
          delete options.icons;
          options.contexts = options.contexts.
            filter((e: string) => CHROME_CONTEXTS.has(e));
          if (!options.contexts.length) {
            return;
          }
        }
        if (options.contexts.includes("all")) {
          alls.set(options.id, options.contexts);
        }
        menus.create(options);
      };
      mcreate({
        id: "DTARegularLink",
        contexts: ["link"],
        icons: {
          16: "/style/button-regular.png",
          32: "/style/button-regular@2x.png",
        },
        title: _("dta.regular.link"),
      });
      mcreate({
        id: "DTATurboLink",
        contexts: ["link"],
        icons: {
          16: "/style/button-turbo.png",
          32: "/style/button-turbo@2x.png",
        },
        title: _("dta.turbo.link"),
      });
      mcreate({
        id: "DTARegularImage",
        contexts: ["image"],
        icons: {
          16: "/style/button-regular.png",
          32: "/style/button-regular@2x.png",
        },
        title: _("dta.regular.image"),
      });
      mcreate({
        id: "DTATurboImage",
        contexts: ["image"],
        icons: {
          16: "/style/button-turbo.png",
          32: "/style/button-turbo@2x.png",
        },
        title: _("dta.turbo.image"),
      });
      mcreate({
        id: "DTARegularMedia",
        contexts: ["video", "audio"],
        icons: {
          16: "/style/button-regular.png",
          32: "/style/button-regular@2x.png",
        },
        title: _("dta.regular.media"),
      });
      mcreate({
        id: "DTATurboMedia",
        contexts: ["video", "audio"],
        icons: {
          16: "/style/button-turbo.png",
          32: "/style/button-turbo@2x.png",
        },
        title: _("dta.turbo.media"),
      });
      mcreate({
        id: "DTARegularSelection",
        contexts: ["selection"],
        icons: {
          16: "/style/button-regular.png",
          32: "/style/button-regular@2x.png",
        },
        title: _("dta.regular.selection"),
      });
      mcreate({
        id: "DTATurboSelection",
        contexts: ["selection"],
        icons: {
          16: "/style/button-turbo.png",
          32: "/style/button-turbo@2x.png",
        },
        title: _("dta.turbo.selection"),
      });
      mcreate({
        id: "DTARegular",
        contexts: ["all", "browser_action", "tools_menu"],
        icons: {
          16: "/style/button-regular.png",
          32: "/style/button-regular@2x.png",
        },
        title: _("dta.regular"),
      });
      mcreate({
        id: "DTATurbo",
        contexts: ["all", "browser_action", "tools_menu"],
        icons: {
          16: "/style/button-turbo.png",
          32: "/style/button-turbo@2x.png",
        },
        title: _("dta.turbo"),
      });
      mcreate({
        id: "sep-1",
        contexts: ["all", "browser_action", "tools_menu"],
        type: "separator"
      });
      mcreate({
        id: "DTARegularAll",
        contexts: ["all", "browser_action", "tools_menu"],
        icons: {
          16: "/style/button-regular.png",
          32: "/style/button-regular@2x.png",
        },
        title: _("dta-regular-all"),
      });
      mcreate({
        id: "DTATurboAll",
        contexts: ["all", "browser_action", "tools_menu"],
        icons: {
          16: "/style/button-turbo.png",
          32: "/style/button-turbo@2x.png",
        },
        title: _("dta-turbo-all"),
      });
      const sep2ctx = menus.ACTION_MENU_TOP_LEVEL_LIMIT === 6 ?
        ["all", "tools_menu"] :
        ["all", "browser_action", "tools_menu"];
      mcreate({
        id: "sep-2",
        contexts: sep2ctx,
        type: "separator"
      });
      mcreate({
        id: "DTAAdd",
        contexts: ["all", "browser_action", "tools_menu"],
        icons: {
          16: "/style/add.svg",
          32: "/style/add.svg",
          64: "/style/add.svg",
          128: "/style/add.svg",
        },
        title: _("add-download"),
      });
      mcreate({
        id: "sep-3",
        contexts: ["all", "browser_action", "tools_menu"],
        type: "separator"
      });
      mcreate({
        id: "DTAManager",
        contexts: ["all", "browser_action", "tools_menu"],
        icons: {
          16: "/style/button-manager.png",
          32: "/style/button-manager@2x.png",
        },
        title: _("manager.short"),
      });
      mcreate({
        id: "DTAPrefs",
        contexts: ["all", "browser_action", "tools_menu"],
        icons: {
          16: "/style/settings.svg",
          32: "/style/settings.svg",
          64: "/style/settings.svg",
          128: "/style/settings.svg",
        },
        title: _("prefs.short"),
      });
      Object.freeze(alls);

      const adjustMenus = (v: boolean) => {
        for (const [id, contexts] of alls.entries()) {
          const adjusted = v ?
            contexts.filter(e => e !== "all") :
            contexts;
          menus.update(id, {
            contexts: adjusted
          });
        }
      };
      Prefs.get("hide-context", false).then((v: boolean) => {
      // This is the initial load, so no need to adjust when visible already
        if (!v) {
          return;
        }
        adjustMenus(v);
      });
      Prefs.on("hide-context", (prefs, key, value: boolean) => {
        adjustMenus(value);
      });

      menus.onClicked.addListener(this.onClicked);
    }

    *makeSingleItemList(url: string, results: any[]) {
      for (const result of results) {
        const finisher = new Finisher(result);
        for (const list of [result.links, result.media]) {
          for (const e of list) {
            if (e.url !== url) {
              continue;
            }
            const finished = finisher.finish(e);
            if (!finished) {
              continue;
            }
            yield finished;
          }
        }
      }
    }

    async findSingleItem(tab: Tab, url: string, turbo = false) {
      if (!url) {
        return;
      }
      const results = await runContentJob(
        tab, "/bundles/content-gather.js", {
          type: "DTA:gather",
          selectionOnly: false,
          schemes: Array.from(ALLOWED_SCHEMES.values()),
          transferable: TRANSFERABLE_PROPERTIES,
        });
      const found = Array.from(this.makeSingleItemList(url, results));
      const unique = makeUniqueItems([found]);
      if (!unique.length) {
        return;
      }
      const [item] = unique;
      API[turbo ? "singleTurbo" : "singleRegular"](item);
    }

    onClicked(info: MenuClickInfo, tab: Tab) {
      if (!tab.id) {
        return;
      }
      const {menuItemId} = info;
      const {[`onClicked${menuItemId}`]: handler}: any = this;
      if (!handler) {
        console.error("Invalid Handler for", menuItemId);
        return;
      }
      const rv: Promise<void> | void = handler.call(this, info, tab);
      if (rv && rv.catch) {
        rv.catch(console.error);
      }
    }

    async emulate(action: string) {
      const tab = await tabs.query({
        active: true,
        currentWindow: true,
      });
      if (!tab || !tab.length) {
        return;
      }
      this.onClicked({
        menuItemId: action
      }, tab[0]);
    }

    async onClickedDTARegular(info: MenuClickInfo, tab: Tab) {
      return await this.performSelection({
        selectionOnly: false,
        allTabs: false,
        turbo: false,
        tab,
      });
    }

    async onClickedDTARegularAll(info: MenuClickInfo, tab: Tab) {
      return await this.performSelection({
        selectionOnly: false,
        allTabs: true,
        turbo: false,
        tab,
      });
    }

    async onClickedDTARegularSelection(info: MenuClickInfo, tab: Tab) {
      return await this.performSelection({
        selectionOnly: true,
        allTabs: false,
        turbo: false,
        tab,
      });
    }

    async onClickedDTATurbo(info: MenuClickInfo, tab: Tab) {
      return await this.performSelection({
        selectionOnly: false,
        allTabs: false,
        turbo: true,
        tab,
      });
    }

    async onClickedDTATurboAll(info: MenuClickInfo, tab: Tab) {
      return await this.performSelection({
        selectionOnly: false,
        allTabs: true,
        turbo: true,
        tab,
      });
    }

    async onClickedDTATurboSelection(info: MenuClickInfo, tab: Tab) {
      return await this.performSelection({
        selectionOnly: true,
        allTabs: false,
        turbo: true,
        tab,
      });
    }

    async onClickedDTARegularLink(info: MenuClickInfo, tab: Tab) {
      if (!info.linkUrl) {
        return;
      }
      await this.findSingleItem(tab, info.linkUrl, false);
    }

    async onClickedDTATurboLink(info: MenuClickInfo, tab: Tab) {
      if (!info.linkUrl) {
        return;
      }
      await this.findSingleItem(tab, info.linkUrl, true);
    }

    async onClickedDTARegularImage(info: MenuClickInfo, tab: Tab) {
      if (!info.srcUrl) {
        return;
      }
      await this.findSingleItem(tab, info.srcUrl, false);
    }

    async onClickedDTATurboImage(info: MenuClickInfo, tab: Tab) {
      if (!info.srcUrl) {
        return;
      }
      await this.findSingleItem(tab, info.srcUrl, true);
    }

    async onClickedDTARegularMedia(info: MenuClickInfo, tab: Tab) {
      if (!info.srcUrl) {
        return;
      }
      await this.findSingleItem(tab, info.srcUrl, false);
    }

    async onClickedDTATurboMedia(info: MenuClickInfo, tab: Tab) {
      if (!info.srcUrl) {
        return;
      }
      await this.findSingleItem(tab, info.srcUrl, true);
    }

    onClickedDTAAdd() {
      API.singleRegular(null);
    }

    async onClickedDTAManager() {
      await openManager();
    }

    async onClickedDTAPrefs() {
      await openPrefs();
    }
  }();

  new class Action extends Handler {
    constructor() {
      super();
      this.onClicked = this.onClicked.bind(this);
      action.onClicked.addListener(this.onClicked);
      Prefs.get("button-type", false).then(v => this.adjust(v));
      Prefs.on("button-type", (prefs, key, value) => {
        this.adjust(value);
      });
    }

    adjust(type: string) {
      action.setPopup({
        popup: type !== "popup" ? "" : "/windows/popup.html"
      });
      let icons;
      switch (type) {
      case "popup":
        icons = {
          16: "/style/icon16.png",
          32: "/style/icon32.png",
          48: "/style/icon48.png",
          64: "/style/icon64.png",
          96: "/style/icon96.png",
          128: "/style/icon128.png",
          256: "/style/icon256.png"
        };
        break;

      case "dta":
        icons = {
          16: "/style/button-regular.png",
          32: "/style/button-regular@2x.png",
        };
        break;

      case "turbo":
        icons = {
          16: "/style/button-turbo.png",
          32: "/style/button-turbo@2x.png",
        };
        break;

      case "manager":
        icons = {
          16: "/style/button-manager.png",
          32: "/style/button-manager@2x.png",
        };
        break;
      }
      action.setIcon({path: icons});
    }

    async onClicked() {
      switch (await Prefs.get("button-type")) {
      case "popup":
        break;

      case "dta":
        menuHandler.emulate("DTARegular");
        break;

      case "turbo":
        menuHandler.emulate("DTATurbo");
        break;

      case "manager":
        menuHandler.emulate("DTAManager");
        break;
      }
    }
  }();


  Bus.on("do-regular", () => menuHandler.emulate("DTARegular"));
  Bus.on("do-regular-all", () => menuHandler.emulate("DTARegularAll"));
  Bus.on("do-turbo", () => menuHandler.emulate("DTATurbo"));
  Bus.on("do-turbo-all", () => menuHandler.emulate("DTATurboAll"));
  Bus.on("do-single", () => API.singleRegular(null));
  Bus.on("open-manager", () => openManager(true));
  Bus.on("open-prefs", () => openPrefs());

  (async function init() {
    const urlBase = runtime.getURL("");
    history.onVisited.addListener(({url}: {url: string}) => {
      if (!url || !url.startsWith(urlBase)) {
        return;
      }
      history.deleteUrl({url});
    });
    const results: {url?: string}[] = await history.search({text: urlBase});
    for (const {url} of results) {
      if (!url) {
        continue;
      }
      history.deleteUrl({url});
    }

    if (!CHROME) {
      const sessionRemover = async () => {
        for (const s of await sessions.getRecentlyClosed()) {
          if (s.tab) {
            if (s.tab.url.startsWith(urlBase)) {
              await sessions.forgetClosedTab(s.tab.windowId, s.tab.sessionId);
            }
            continue;
          }
          if (!s.window || !s.window.tabs || s.window.tabs.length > 1) {
            continue;
          }
          const [tab] = s.window.tabs;
          if (tab.url.startsWith(urlBase)) {
            await sessions.forgetClosedWindow(s.window.sessionId);
          }
        }
      };
      sessions.onChanged.addListener(sessionRemover);
      await sessionRemover();
    }

    await Prefs.set("last-run", new Date());
    await filters();
    await getManager();
  })().catch(ex => {
    console.error("Failed to init components", ex.toString(), ex.stack, ex);
  });
});
