// PDF outline/TOC navigation
// Procedural implementation (no OOP)

interface PDFOutlineNode {
  id: string;
  title: string;
  destination: number | null;
  pageNumber?: number;
  children: PDFOutlineNode[];
  expanded: boolean;
}

interface PDFOutlineState {
  doc: PDFDocumentProxy | null;
  outline: PDFOutlineNode[];
  flatMap: Map<string, number>;
}

async function initializePDFOutline(
  doc: PDFDocumentProxy,
): Promise<PDFOutlineState> {
  const state: PDFOutlineState = {
    doc,
    outline: [],
    flatMap: new Map(),
  };

  return await loadPDFOutline(state);
}

async function loadPDFOutline(
  state: PDFOutlineState,
): Promise<PDFOutlineState> {
  if (!state.doc) return state;

  const pdfOutline = await state.doc.getOutline();

  if (!pdfOutline || pdfOutline.length === 0) {
    return { ...state, outline: [] };
  }

  const outline = await parseOutlineNodes(state, pdfOutline);

  return { ...state, outline };
}

async function parseOutlineNodes(
  state: PDFOutlineState,
  nodes: OutlineTreeNode[],
): Promise<PDFOutlineNode[]> {
  const result: PDFOutlineNode[] = [];

  for (const node of nodes) {
    const outlineNode: PDFOutlineNode = {
      id: generateOutlineId(),
      title: node.title,
      destination: null,
      children: [],
      expanded: false,
    };

    if (node.dest) {
      const pageNumber = await resolvePDFDestination(state, node.dest);
      outlineNode.destination = pageNumber;
      outlineNode.pageNumber = pageNumber;
      state.flatMap.set(node.title, pageNumber);
    }

    if (node.items && node.items.length > 0) {
      outlineNode.children = await parseOutlineNodes(state, node.items);
    }

    result.push(outlineNode);
  }

  return result;
}

async function resolvePDFDestination(
  state: PDFOutlineState,
  dest: string | any[],
): Promise<number> {
  if (!state.doc) return 1;

  try {
    let explicitDest: any[];

    if (typeof dest === "string") {
      const destObj = await state.doc.getDestination(dest);
      if (!destObj) return 1;
      explicitDest = destObj;
    } else {
      explicitDest = dest;
    }

    const ref = explicitDest[0];

    if (typeof ref === "object" && ref !== null) {
      const pageIndex = await state.doc.getPageIndex(ref);
      return pageIndex + 1;
    } else if (typeof ref === "number") {
      return ref + 1;
    }

    return 1;
  } catch (error) {
    console.error("Failed to resolve destination:", dest, error);
    return 1;
  }
}

function generateOutlineId(): string {
  return `outline-${Math.random().toString(36).substr(2, 9)}`;
}

function getOutline(state: PDFOutlineState): PDFOutlineNode[] {
  return state.outline;
}

function getOutlineFlatMap(state: PDFOutlineState): Map<string, number> {
  return state.flatMap;
}

function getCurrentChapter(
  state: PDFOutlineState,
  pageNumber: number,
): PDFOutlineNode | null {
  return findChapterForPage(state.outline, pageNumber);
}

function findChapterForPage(
  nodes: PDFOutlineNode[],
  pageNumber: number,
): PDFOutlineNode | null {
  for (const node of nodes) {
    if (node.pageNumber && node.pageNumber <= pageNumber) {
      if (node.children.length > 0) {
        const childMatch = findChapterForPage(node.children, pageNumber);
        if (childMatch) return childMatch;
      }
      return node;
    }

    if (node.children.length > 0) {
      const childMatch = findChapterForPage(node.children, pageNumber);
      if (childMatch) return childMatch;
    }
  }

  return null;
}

function toggleOutlineNode(
  state: PDFOutlineState,
  nodeId: string,
): PDFOutlineState {
  const updateNode = (nodes: PDFOutlineNode[]): PDFOutlineNode[] => {
    return nodes.map((node) => {
      if (node.id === nodeId) {
        return { ...node, expanded: !node.expanded };
      }
      if (node.children.length > 0) {
        return { ...node, children: updateNode(node.children) };
      }
      return node;
    });
  };

  return { ...state, outline: updateNode(state.outline) };
}

function findOutlineNode(
  nodes: PDFOutlineNode[],
  id: string,
): PDFOutlineNode | null {
  for (const node of nodes) {
    if (node.id === id) return node;
    if (node.children.length > 0) {
      const found = findOutlineNode(node.children, id);
      if (found) return found;
    }
  }
  return null;
}
