import type {
  Journey,
  JourneyNode,
  JourneyEdge,
  JourneyNodeId,
  JourneyEdgeId,
} from './journey-store.js';
import type { Guideline, GuidelineId, Criticality } from './guideline-store.js';

export interface ProjectedGuideline {
  id: GuidelineId;
  content: {
    condition: string;
    action: string | null;
    description: string | null;
  };
  criticality: Criticality;
  creationUtc: string;
  enabled: boolean;
  tags: string[];
  labels: string[];
  metadata: Record<string, unknown>;
}

function formatNodeGuidelineId(nodeId: JourneyNodeId, edgeId?: JourneyEdgeId | null): GuidelineId {
  return `journey_node:${nodeId}${edgeId ? `:${edgeId}` : ''}` as GuidelineId;
}

export function projectJourneyToGuidelines(
  journey: Journey,
  nodes: JourneyNode[],
  edges: JourneyEdge[],
): ProjectedGuideline[] {
  const nodeMap = new Map<JourneyNodeId, JourneyNode>();
  for (const n of nodes) nodeMap.set(n.id, n);

  const edgeMap = new Map<JourneyEdgeId, JourneyEdge>();
  for (const e of edges) edgeMap.set(e.id, e);

  const nodeEdges = new Map<JourneyNodeId, JourneyEdge[]>();
  for (const e of edges) {
    const list = nodeEdges.get(e.source) || [];
    list.push(e);
    nodeEdges.set(e.source, list);
  }

  const guidelines: Map<GuidelineId, ProjectedGuideline> = new Map();
  const nodeIndexes = new Map<JourneyNodeId, number>();
  let index = 0;

  const queue: Array<{ edgeId: JourneyEdgeId | null; nodeId: JourneyNodeId }> = [];
  const visited = new Set<string>();

  queue.push({ edgeId: null, nodeId: journey.rootId });

  while (queue.length > 0) {
    const { edgeId, nodeId } = queue.shift()!;
    const visitKey = `${edgeId || ''}:${nodeId}`;
    if (visited.has(visitKey)) continue;
    visited.add(visitKey);

    const node = nodeMap.get(nodeId);
    if (!node) continue;

    if (!nodeIndexes.has(nodeId)) {
      index++;
      nodeIndexes.set(nodeId, index);
    }

    const edge = edgeId ? edgeMap.get(edgeId) : undefined;

    const baseJourneyNode: Record<string, unknown> = {
      follow_ups: [],
      index: String(nodeIndexes.get(nodeId)),
      journey_id: journey.id,
      labels: node.labels,
      tool_ids: node.tools,
    };

    const edgeJourneyNode = (edge?.metadata?.['journey_node'] as Record<string, unknown>) || {};
    const nodeJourneyNode = (node.metadata?.['journey_node'] as Record<string, unknown>) || {};

    const mergedJourneyNode = { ...baseJourneyNode, ...nodeJourneyNode, ...edgeJourneyNode };

    const metadata: Record<string, unknown> = {
      journey_node: mergedJourneyNode,
    };
    for (const [k, v] of Object.entries(node.metadata)) {
      if (k !== 'journey_node') metadata[k] = v;
    }
    if (edge) {
      for (const [k, v] of Object.entries(edge.metadata)) {
        if (k !== 'journey_node') metadata[k] = v;
      }
    }

    const gid = formatNodeGuidelineId(nodeId, edgeId);
    const guideline: ProjectedGuideline = {
      id: gid,
      content: {
        condition: (edge?.condition) || '',
        action: node.action,
        description: node.description,
      },
      criticality: 'high' as Criticality,
      creationUtc: new Date().toISOString(),
      enabled: true,
      tags: journey.tags,
      labels: [...(node.labels || [])],
      metadata,
    };

    guidelines.set(gid, guideline);

    const childEdges = nodeEdges.get(nodeId) || [];
    for (const childEdge of childEdges) {
      if (visited.has(`${childEdge.id}:${childEdge.target}`)) continue;
      queue.push({ edgeId: childEdge.id, nodeId: childEdge.target });

      const childGid = formatNodeGuidelineId(childEdge.target, childEdge.id);
      const followUps = (guideline.metadata['journey_node'] as Record<string, unknown>)['follow_ups'] as string[];
      if (!followUps.includes(childGid)) {
        followUps.push(childGid);
      }
    }
  }

  return [...guidelines.values()];
}

export interface BacktrackCheck {
  journeyId: string;
  currentNodeId: JourneyNodeId;
  previousNodeId: JourneyNodeId;
  isBacktrack: boolean;
  recommendation: string | null;
}

export function detectJourneyBacktrack(
  journey: Journey,
  nodes: JourneyNode[],
  edges: JourneyEdge[],
  currentNodeId: JourneyNodeId,
  previousNodeId: JourneyNodeId,
): BacktrackCheck {
  const nodeMap = new Map<JourneyNodeId, JourneyNode>();
  for (const n of nodes) nodeMap.set(n.id, n);

  const adjacency = new Map<JourneyNodeId, JourneyNodeId[]>();
  for (const e of edges) {
    const list = adjacency.get(e.source) || [];
    list.push(e.target);
    adjacency.set(e.source, list);
  }

  const isInForwardPath = (from: JourneyNodeId, target: JourneyNodeId): boolean => {
    const visitedInner = new Set<JourneyNodeId>();
    const queueInner: JourneyNodeId[] = [from];
    while (queueInner.length > 0) {
      const current = queueInner.shift()!;
      if (current === target) return true;
      if (visitedInner.has(current)) continue;
      visitedInner.add(current);
      for (const next of adjacency.get(current) || []) {
        if (!visitedInner.has(next)) queueInner.push(next);
      }
    }
    return false;
  };

  const fromCurToPrev = isInForwardPath(currentNodeId, previousNodeId);
  const fromPrevToCur = isInForwardPath(previousNodeId, currentNodeId);

  const isBacktrack = !fromPrevToCur && !fromCurToPrev;

  let recommendation: string | null = null;
  if (isBacktrack && nodeMap.has(previousNodeId)) {
    const prevNode = nodeMap.get(previousNodeId)!;
    recommendation = `Detected potential backtrack from '${currentNodeId}' to '${previousNodeId}' (${prevNode.action || 'no action'}). Consider whether this regression is intentional.`;
  }

  return {
    journeyId: journey.id,
    currentNodeId,
    previousNodeId,
    isBacktrack,
    recommendation,
  };
}
