import { scanWithFacts, type ScanFile } from "../typestyle/check.js";
import type {
  ScanSourceFile,
  ScanSourceWithFactsInput,
  ScanSourceWithFactsResult,
} from "./types.js";
import { isSupportedScanPath, normalizeScanSourcePath, syntheticAbsPath } from "./paths.js";

const SOURCE_READ_CONCURRENCY = 12;

function toScanFile(
  input: ScanSourceWithFactsInput,
  file: ScanSourceFile,
  relPath: string,
  content: string
): ScanFile {
  return {
    absPath: file.absPath ?? syntheticAbsPath(input.source.root, relPath),
    relPath,
    content,
  };
}

export async function scanSourceWithFacts(
  input: ScanSourceWithFactsInput
): Promise<ScanSourceWithFactsResult> {
  const seen = new Set<string>();
  const candidates = input.files.flatMap((file) => {
    const relPath = normalizeScanSourcePath(file.relPath);
    if (!relPath || !isSupportedScanPath(relPath)) return [];
    if (seen.has(relPath)) return [];
    seen.add(relPath);
    return [{ file, relPath }];
  });

  const files = (
    await mapWithConcurrency(candidates, SOURCE_READ_CONCURRENCY, async ({ file, relPath }) => {
      const content = await input.source.readFile(relPath);
      if (content === null) return null;
      return toScanFile(input, file, relPath, content);
    })
  ).filter((file): file is ScanFile => file !== null);

  return scanWithFacts({ facts: input.facts, files });
}

async function mapWithConcurrency<T, R>(
  items: readonly T[],
  concurrency: number,
  mapper: (item: T) => Promise<R>
): Promise<R[]> {
  const out = new Array<R>(items.length);
  let nextIndex = 0;
  const workerCount = Math.min(concurrency, items.length);

  async function worker(): Promise<void> {
    while (nextIndex < items.length) {
      const index = nextIndex++;
      out[index] = await mapper(items[index]!);
    }
  }

  await Promise.all(Array.from({ length: workerCount }, () => worker()));
  return out;
}
