import type { SemanticProgramIr, SemanticSourceMapEntry } from "../grl/ir/index.js";
import type { MotionSourceMap } from "../kdl/types.js";

export interface RuntimeSourceLocation {
  kind: string;
  id?: string;
  sourceMap?: MotionSourceMap;
  pathId?: string;
  pointId?: string;
  operationId?: string;
  procedureId?: string;
  brand?: {
    vendor?: string;
    file?: string;
    line?: number;
    column?: number;
    symbol?: string;
  };
}

export class RuntimeSourceMapIndex {
  private readonly entries: SemanticSourceMapEntry[];

  constructor(program: Pick<SemanticProgramIr, "sourceMap"> | SemanticSourceMapEntry[] = []) {
    this.entries = Array.isArray(program) ? [...program] : [...program.sourceMap];
  }

  all(): RuntimeSourceLocation[] {
    return this.entries.map((entry) => this.toLocation(entry));
  }

  locateByInstruction(input: {
    kind?: string;
    sourceMap?: MotionSourceMap;
    pathId?: string;
    pointId?: string;
    operationId?: string;
    procedureId?: string;
    id?: string;
    source?: Record<string, unknown>;
  }): RuntimeSourceLocation | undefined {
    const sourceLine = input.sourceMap?.line;
    const sourceFile = input.sourceMap?.file;
    const entry = this.entries.find((candidate) => {
      if (input.pathId && candidate.pathId !== input.pathId) {
        return false;
      }
      if (input.pointId && candidate.pointId !== input.pointId) {
        return false;
      }
      if (input.operationId && candidate.operationId !== input.operationId) {
        return false;
      }
      if (input.procedureId && candidate.procedureId !== input.procedureId) {
        return false;
      }
      if (input.kind && candidate.kind !== input.kind && candidate.id !== input.id) {
        return false;
      }
      if (sourceLine !== undefined && candidate.sourceMap.line !== sourceLine) {
        return false;
      }
      if (sourceFile !== undefined && candidate.sourceMap.file !== sourceFile) {
        return false;
      }
      return true;
    });

    if (entry) {
      return this.toLocation(entry, input.source);
    }

    if (input.sourceMap || input.pathId || input.operationId || input.pointId || input.procedureId) {
      const brand = brandSource(input.source);
      return {
        kind: input.kind ?? "instruction",
        ...(input.id ? { id: input.id } : {}),
        ...(input.sourceMap ? { sourceMap: input.sourceMap } : {}),
        ...(input.pathId ? { pathId: input.pathId } : {}),
        ...(input.pointId ? { pointId: input.pointId } : {}),
        ...(input.operationId ? { operationId: input.operationId } : {}),
        ...(input.procedureId ? { procedureId: input.procedureId } : {}),
        ...(brand ? { brand } : {})
      };
    }

    return undefined;
  }

  locatePathPoint(pathId: string, pointId: string): RuntimeSourceLocation | undefined {
    return this.find((entry) => entry.pathId === pathId && entry.pointId === pointId);
  }

  locateOperation(operationId: string): RuntimeSourceLocation[] {
    return this.entries
      .filter((entry) => entry.operationId === operationId)
      .map((entry) => this.toLocation(entry));
  }

  locateSource(sourceMap: MotionSourceMap): RuntimeSourceLocation[] {
    return this.entries
      .filter((entry) =>
        (sourceMap.file === undefined || entry.sourceMap.file === sourceMap.file) &&
        (sourceMap.line === undefined || entry.sourceMap.line === sourceMap.line) &&
        (sourceMap.column === undefined || entry.sourceMap.column === sourceMap.column)
      )
      .map((entry) => this.toLocation(entry));
  }

  private find(predicate: (entry: SemanticSourceMapEntry) => boolean): RuntimeSourceLocation | undefined {
    const entry = this.entries.find(predicate);
    return entry ? this.toLocation(entry) : undefined;
  }

  private toLocation(entry: SemanticSourceMapEntry, source?: Record<string, unknown>): RuntimeSourceLocation {
    const brand = brandSource(source);
    return {
      kind: entry.kind,
      id: entry.id,
      sourceMap: entry.sourceMap,
      ...(entry.pathId ? { pathId: entry.pathId } : {}),
      ...(entry.pointId ? { pointId: entry.pointId } : {}),
      ...(entry.operationId ? { operationId: entry.operationId } : {}),
      ...(entry.procedureId ? { procedureId: entry.procedureId } : {}),
      ...(brand ? { brand } : {})
    };
  }
}

function brandSource(source?: Record<string, unknown>): RuntimeSourceLocation["brand"] | undefined {
  const brand = source?.brand;
  if (!brand || typeof brand !== "object") {
    return undefined;
  }
  const record = brand as Record<string, unknown>;
  return {
    ...(typeof record.vendor === "string" ? { vendor: record.vendor } : {}),
    ...(typeof record.file === "string" ? { file: record.file } : {}),
    ...(typeof record.line === "number" ? { line: record.line } : {}),
    ...(typeof record.column === "number" ? { column: record.column } : {}),
    ...(typeof record.symbol === "string" ? { symbol: record.symbol } : {})
  };
}
