// EPUB CFI (Canonical Fragment Identifier) navigation
// Reuses logic from internal/sync/format.go
// Procedural style: Functions, not classes

interface CFIComponent {
  type: "index" | "indirection-step" | "text-location";
  value: number;
  id?: string;
  textOffset?: number;
}

// ============================================================
// CFI Parsing Functions
// ============================================================

export function parseCFI(cfi: string): CFIComponent[] {
  const components: CFIComponent[] = [];

  const cleanCFI = cfi.startsWith("!") ? cfi.substring(1) : cfi;
  const parts = cleanCFI.split("/").filter(Boolean);

  for (const part of parts) {
    const match = part.match(/^(\d+)(?:\[([^\]]+)\])?(?::(\d+))?$/);
    if (match) {
      const component: CFIComponent = {
        type: match[3] !== undefined ? "text-location" : "index",
        value: parseInt(match[1], 10),
        id: match[2],
        textOffset: match[3] !== undefined ? parseInt(match[3], 10) : undefined,
      };

      components.push(component);
    }
  }

  return components;
}

export function generateCFI(
  spineIndex: number,
  elementPath: number[],
  textOffset: number = 0,
  spineItemId?: string,
): string {
  let cfi = `/6/${spineIndex}`;

  if (spineItemId) {
    cfi += `[${spineItemId}]`;
  }

  for (const index of elementPath) {
    cfi += `/${index}`;
  }

  if (textOffset > 0) {
    cfi += `:${textOffset}`;
  }

  return cfi;
}

export function navigateToCFI(
  doc: Document,
  cfi: string,
): Element | Text | null {
  const components = parseCFI(cfi);

  if (components.length === 0) return null;

  let current: Node | null = doc.body;

  for (let i = 1; i < components.length; i++) {
    const component = components[i];

    if (component.type === "index") {
      if (current instanceof Element) {
        const children = getElementChildren(current);
        current = children[component.value] || null;
      }
    }
  }

  return current as Element | Text;
}

export function getSelectionCFI(doc: Document): string | null {
  const selection = window.getSelection();
  if (!selection || selection.rangeCount === 0) return null;

  const range = selection.getRangeAt(0);
  const startContainer = range.startContainer;

  // Build path to start container
  const path: number[] = [];
  let current: Node | null = startContainer;

  while (current && current !== doc.body) {
    const parent = current.parentElement;
    if (parent) {
      const siblings = getElementChildren(parent);
      const index = siblings.indexOf(current as Element);
      path.unshift(index);
    }
    current = parent;
  }

  const spineIndex = 0;
  const textOffset = range.startOffset;

  return generateCFI(spineIndex, path, textOffset);
}

export function getPercentageFromCFI(cfi: string): number {
  const components = parseCFI(cfi);
  const textLocation = components.find((c) => c.type === "text-location");

  if (textLocation && textLocation.textOffset !== undefined) {
    return Math.min(textLocation.textOffset / 10, 100);
  }

  return 0;
}

export function compareCFIs(cfi1: string, cfi2: string): number {
  const components1 = parseCFI(cfi1);
  const components2 = parseCFI(cfi2);

  const maxLen = Math.max(components1.length, components2.length);

  for (let i = 0; i < maxLen; i++) {
    const comp1 = components1[i];
    const comp2 = components2[i];

    if (!comp1) return -1;
    if (!comp2) return 1;

    if (comp1.value !== comp2.value) {
      return comp1.value - comp2.value;
    }
  }

  return 0;
}

function getElementChildren(element: Element): Element[] {
  return Array.from(element.children).filter(
    (el) => el.nodeType === Node.ELEMENT_NODE,
  ) as Element[];
}
