// Search within ebook content
// Feature Registration Pattern implementation

import type { ReaderContext } from "../core/reader-context";

export function init(context: ReaderContext): void {
  let ebookData: any = null;

  context.events.on("reader:loaded", (detail: { ebookData: any }) => {
    ebookData = detail.ebookData;
  });

  context.events.on("search:execute", async (detail: { query: string }) => {
    if (ebookData) {
      const results = await searchEbook(ebookData, detail.query);
      context.events.emit("search:results", { results });
    }
  });

  context.events.on("reader:unload", () => {
    ebookData = null;
  });
}

interface SearchResult {
  cfi: string;
  snippet: string;
  chapterTitle: string;
}

export async function searchEbook(
  ebookData: any,
  query: string,
): Promise<SearchResult[]> {
  const results: SearchResult[] = [];
  const lowerQuery = query.toLowerCase();

  if (!ebookData.spine) return results;

  for (const spineItem of ebookData.spine) {
    const doc = await getSpineItemDocument(ebookData, spineItem);

    if (!doc) continue;

    const chapterTitle = getChapterTitle(spineItem);
    const textNodes = findTextNodes(doc.body);

    for (const node of textNodes) {
      const text = node.textContent || "";
      const lowerText = text.toLowerCase();

      let foundAt = 0;
      while ((foundAt = lowerText.indexOf(lowerQuery, foundAt)) !== -1) {
        const cfi = generateCFIForNode(node, foundAt);
        const snippet = extractSnippet(text, foundAt, query.length);

        results.push({
          cfi,
          snippet,
          chapterTitle,
        });

        foundAt += lowerQuery.length;
      }
    }
  }

  return results;
}

async function getSpineItemDocument(
  ebookData: any,
  spineItem: any,
): Promise<Document | null> {
  try {
    const resources = ebookData.resources;
    if (!resources) return null;

    const content = await resources.get(spineItem.href)?.text();
    if (!content) return null;

    const parser = new DOMParser();
    return parser.parseFromString(content, "text/html");
  } catch (error) {
    console.error("Failed to load spine item:", spineItem.href, error);
    return null;
  }
}

function getChapterTitle(spineItem: any): string {
  return spineItem.id || `Section ${spineItem.index || ""}`;
}

function findTextNodes(root: Node): Text[] {
  const textNodes: Text[] = [];
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
    acceptNode: (node) => {
      const parent = node.parentElement;
      if (parent && ["SCRIPT", "STYLE", "NOSCRIPT"].includes(parent.tagName)) {
        return NodeFilter.FILTER_REJECT;
      }

      if (!node.textContent?.trim()) {
        return NodeFilter.FILTER_REJECT;
      }

      return NodeFilter.FILTER_ACCEPT;
    },
  });

  let node: Node | null;
  while ((node = walker.nextNode())) {
    textNodes.push(node as Text);
  }

  return textNodes;
}

function generateCFIForNode(node: Node, offset: number): string {
  const path: number[] = [];
  let current: Node | null = node;

  while (current && current.parentNode) {
    const siblings = Array.from(current.parentNode.childNodes);
    const index = siblings.indexOf(current as ChildNode);
    path.unshift(index);
    current = current.parentNode;
  }

  return `/6/4${path.map((i) => `/${i + 2}`).join("")}:${offset}`;
}

function extractSnippet(text: string, offset: number, length: number): string {
  const start = Math.max(0, offset - 40);
  const end = Math.min(text.length, offset + length + 40);
  let snippet = text.substring(start, end);

  if (start > 0) snippet = "..." + snippet;
  if (end < text.length) snippet = snippet + "...";

  return snippet;
}