import { effectiveDefinitions } from "./definitions";
import { DefinitionEngine, hasPotentialDefinitionForUrl, selectDefinition, selectDefinitionForUrl } from "./definition-engine";
import { applyResult, hasAppliedFilter, removeOrphanedPlaceholders, restore } from "./filter";
import { loadSettings } from "../shared/settings";
import { hashText } from "../shared/hash";
import { priorityFor } from "../inference/queue";
import type { InferenceRequest, InferenceResponse, PageStatus, RuntimeMessage, Settings } from "../shared/types";

void start();

async function start(): Promise<void> {
  const settingsPromise = requestSettings();
  let settings: Settings;
  console.debug("[VibeGuard] Content script status listener starting", { url: location.href });
  let stopCurrent = (): void => {};
  let reloadSequence = 0;

  chrome.runtime.onMessage.addListener((message: RuntimeMessage, _sender, sendResponse) => {
    if (message.type !== "GET_PAGE_STATUS") return false;
    console.debug("[VibeGuard] Page status requested", { url: location.href });
    settingsPromise.then((initialSettings) => getPageStatus(initialSettings)).then((status) => {
      console.debug("[VibeGuard] Sending page status", status);
      sendResponse({ type: "PAGE_STATUS", status });
    });
    return true;
  });

  settings = await settingsPromise;

  const restart = (): void => {
    stopCurrent();
    restoreAllFilters();
    currentReloadSequence = ++reloadSequence;
    stopCurrent = runDefinition(settings, currentReloadSequence);
  };

  chrome.storage.onChanged.addListener(async (changes, area) => {
    if (area !== "local" || !changes["vibeguard.settings"]?.newValue) return;
    settings = await loadSettings();
    restart();
  });

  restart();
  window.addEventListener("pagehide", () => stopCurrent());
}

function runDefinition(settings: Settings, sequence: number): () => void {
  const definitions = effectiveDefinitions(settings);
  let stopCurrentDefinition = noop;
  let stopped = false;
  const definitionObserver = new MutationObserver(() => {
    if (stopped || stopCurrentDefinition !== noop) return;
    const definition = selectDefinition(definitions);
    if (definition) {
      stopCurrentDefinition = startDefinition(definition);
      definitionObserver.disconnect();
    }
  });

  const initialDefinition = selectDefinition(definitions);
  if (initialDefinition) {
    stopCurrentDefinition = startDefinition(initialDefinition);
  } else if (hasPotentialDefinitionForUrl(definitions)) {
    // Social sites can add their marker elements after document_idle. This also
    // covers instance-based sites (such as Mastodon) whose URL pattern is broad.
    definitionObserver.observe(document.documentElement, { childList: true, subtree: true, attributes: true });
  }

  return () => {
    stopped = true;
    definitionObserver.disconnect();
    stopCurrentDefinition();
  };

  function startDefinition(definition: ReturnType<typeof selectDefinition>): () => void {
    if (!definition) return noop;
    console.debug("[VibeGuard] Supported page detected", { definitionId: definition.id, definitionName: definition.name, url: location.href });
    const parser = new DefinitionEngine(definition);
  const navigationId = crypto.randomUUID();
  const fingerprints = new WeakMap<Element, string>();
  const results = new Map<string, Parameters<typeof applyResult>[1]>();
  const revealedPostIds = new Set<string>();
  let stopped = false;

  const reconcileFilters = (): void => {
    if (stopped) return;
    const elementsById = new Map<string, Element[]>();
    document.querySelectorAll("[data-vibeguard-id]").forEach((element) => {
      const id = element.getAttribute("data-vibeguard-id");
      if (!id) return;
      const elements = elementsById.get(id) ?? [];
      elements.push(element);
      elementsById.set(id, elements);
    });

    for (const [id, result] of results) {
      const elements = elementsById.get(id) ?? [];
      const shouldFilter = result.label === "toxic" && result.probability >= settings.threshold && !revealedPostIds.has(id);
      for (const element of elements) {
        if (!shouldFilter) restore(element, id);
        else if (!hasAppliedFilter(element, id, settings)) {
          applyResult(element, result, settings, {
            postId: id,
            onShow: () => { revealedPostIds.add(id); reconcileFilters(); }
          });
        }
      }
      removeOrphanedPlaceholders(id, elements);
    }
  };

  const process = (posts: ReturnType<typeof parser.discover>, source: "initial" | "mutation"): void => {
    if (stopped) return;
    const newPosts = posts.filter((post) => {
      const fingerprint = `${post.id}:${hashText(post.text)}`;
      if (fingerprints.get(post.element) === fingerprint) return false;
      fingerprints.set(post.element, fingerprint);
      return true;
    });
    if (source === "initial" || newPosts.length > 0) {
      console.debug("[VibeGuard] Posts discovered for analysis", {
        definitionId: definition.id,
        source,
        count: newPosts.length,
        posts: newPosts.map((post) => ({ id: post.id, text: post.text, textHash: hashText(post.text) }))
      });
    }
    newPosts.forEach((post) => {
      const request: InferenceRequest = {
        id: post.id, text: post.text, site: definition.id, navigationId,
        priority: priorityFor(isVisible(post.element), document.visibilityState === "visible"),
        requestId: crypto.randomUUID()
      };
      void chrome.runtime.sendMessage<RuntimeMessage, InferenceResponse>({ type: "INFER", request })
        .then((response) => {
          if (response && "error" in response) {
            console.error("[VibeGuard] Post analysis failed", { definitionId: definition.id, id: post.id, requestId: request.requestId, error: response.error });
            return;
          }
          if (stopped || sequence !== currentReloadSequence || !response?.result || !post.element.isConnected) return;
          results.set(post.id, response.result);
          reconcileFilters();
        })
        .catch((error: unknown) => {
          console.error("[VibeGuard] Post analysis failed", {
            definitionId: definition.id,
            id: post.id,
            requestId: request.requestId,
            error: error instanceof Error ? error.message : String(error)
          });
        });
    });
    reconcileFilters();
  };

  process(parser.discover(document), "initial");
  const stopObserver = parser.observe((posts) => process(posts, "mutation"));
    return () => {
    stopped = true;
    stopObserver();
    parser.dispose();
  };
  }
}

const noop = (): void => {};

let currentReloadSequence = 0;

async function getPageStatus(settings: Settings): Promise<PageStatus> {
  const definitions = effectiveDefinitions({ ...settings, disabledDefinitionIds: [] });
  const definition = selectDefinition(definitions) ?? selectDefinitionForUrl(definitions);
  if (!definition) {
    console.debug("[VibeGuard] No matching definition for page status", { url: location.href });
    return { supported: false, paused: false };
  }
  const status = {
    supported: true,
    paused: settings.disabledDefinitionIds.includes(definition.id),
    definition: { id: definition.id, name: definition.name }
  };
  console.debug("[VibeGuard] Matched definition for page status", status);
  return status;
}

function restoreAllFilters(): void {
  document.querySelectorAll('[data-vibeguard-hidden="true"]').forEach((element) => restore(element));
  document.querySelectorAll('[data-vibeguard-placeholder="true"]').forEach((element) => element.remove());
}

async function requestSettings(): Promise<Settings> {
  const response = await chrome.runtime.sendMessage({ type: "GET_SETTINGS" });
  return response?.settings ?? loadSettings();
}

function isVisible(element: Element): boolean {
  const rect = element.getBoundingClientRect();
  return rect.bottom >= 0 && rect.top <= window.innerHeight;
}
