import type {
  LabelEntry,
  SessionTreeEntry,
  SessionTreeLabelState,
} from './types.js';

export const ROOT_PARENT_KEY = '__root__';

export function buildEntryIndex(entries: SessionTreeEntry[]): Record<string, SessionTreeEntry> {
  return entries.reduce<Record<string, SessionTreeEntry>>((index, entry) => {
    index[entry.id] = entry;
    return index;
  }, createRecord<SessionTreeEntry>());
}

export function getPathToLeaf(entries: SessionTreeEntry[], leafEntryId: string): SessionTreeEntry[] {
  const index = buildEntryIndex(entries);
  const leaf = index[leafEntryId];

  if (!leaf) {
    throw new Error(`Entry ${leafEntryId} not found`);
  }

  const path: SessionTreeEntry[] = [];
  const visited = new Set<string>();
  let current: SessionTreeEntry | undefined = leaf;

  while (current) {
    if (visited.has(current.id)) {
      throw new Error('Cycle detected');
    }

    visited.add(current.id);
    path.push(current);

    if (current.parentId === null) {
      break;
    }

    const parent = index[current.parentId];
    if (!parent) {
      throw new Error(`Parent entry ${current.parentId} not found`);
    }

    current = parent;
  }

  return path.reverse();
}

export function findCommonAncestorEntryId(
  entries: SessionTreeEntry[],
  entryIdA: string,
  entryIdB: string,
): string | null {
  const pathA = getPathToLeaf(entries, entryIdA);
  const pathB = getPathToLeaf(entries, entryIdB);

  let ancestorId: string | null = null;
  const length = Math.min(pathA.length, pathB.length);

  for (let index = 0; index < length; index += 1) {
    if (pathA[index]?.id !== pathB[index]?.id) {
      break;
    }
    ancestorId = pathA[index]?.id ?? null;
  }

  return ancestorId;
}

export function groupChildrenByParent(entries: SessionTreeEntry[]): Record<string, string[]> {
  for (const entry of entries) {
    if (entry.id === ROOT_PARENT_KEY) {
      throw new Error(`Entry id ${ROOT_PARENT_KEY} conflicts with ROOT_PARENT_KEY`);
    }
  }

  return entries.reduce<Record<string, string[]>>((groups, entry) => {
    const key = entry.parentId ?? ROOT_PARENT_KEY;
    if (!groups[key]) {
      groups[key] = [];
    }
    groups[key].push(entry.id);
    return groups;
  }, createRecord<string[]>());
}

export function resolveLatestLabels(entries: SessionTreeEntry[]): Record<string, SessionTreeLabelState> {
  const labelsByEntryId = createRecord<SessionTreeLabelState>();
  const latestTimestampsByEntryId = createRecord<string>();

  for (const entry of entries) {
    if (entry.type !== 'label') {
      continue;
    }

    applyLabelEntry(labelsByEntryId, latestTimestampsByEntryId, entry);
  }

  return labelsByEntryId;
}

function applyLabelEntry(
  labelsByEntryId: Record<string, SessionTreeLabelState>,
  latestTimestampsByEntryId: Record<string, string>,
  entry: LabelEntry,
): void {
  const latestTimestamp = latestTimestampsByEntryId[entry.targetId];
  if (latestTimestamp && latestTimestamp >= entry.timestamp) {
    return;
  }

  latestTimestampsByEntryId[entry.targetId] = entry.timestamp;

  if (entry.label === undefined) {
    delete labelsByEntryId[entry.targetId];
    return;
  }

  labelsByEntryId[entry.targetId] = {
    label: entry.label,
    timestamp: entry.timestamp,
    entryId: entry.id,
  };
}

function createRecord<T>(): Record<string, T> {
  return Object.create(null) as Record<string, T>;
}
