import type { DiagramType, SelectedElement } from "./types";

const SEMANTIC_RULES: Record<DiagramType, string> = {
  bpmn: `- Check for processes without error boundaries or exception handling
- Check for gateways without corresponding merge/join
- Check for pools without inter-pool message flows
- Check for missing end events in subprocess branches`,
  er: `- Check for M:N relationships that may need a junction/associative table
- Check for entities without primary keys
- Check for potential circular foreign key dependencies
- Check for denormalization opportunities or concerns`,
  orgchart: `- Check for employees without managers (except root)
- Check for excessive span of control (>10 direct reports)`,
  architecture: `- Check for single points of failure
- Check for services without database connections when data persistence is expected
- Check for missing load balancers in multi-instance deployments`,
  sequence: `- Check for messages without return responses
- Check for participants with no interactions`,
  flowchart: `- Check for unreachable nodes
- Check for decisions with single outgoing path
- Check for missing terminal nodes`,
};

const DIAGRAM_DESCRIPTIONS: Record<DiagramType, string> = {
  bpmn: "BPMN (Business Process Model and Notation) — processes with activities, gateways, events, pools, and lanes",
  er: "Entity-Relationship — database schemas with entities, attributes, and relationships (1:1, 1:N, M:N)",
  orgchart: "Organization Chart — hierarchical structures with people, roles, and reporting lines",
  architecture: "Architecture — system components including services, databases, queues, load balancers, and external systems",
  sequence: "Sequence — interactions between participants over time with synchronous, asynchronous, and return messages",
  flowchart: "Flowchart — decision flows with processes, decisions, terminals, I/O, and subprocesses",
};

const NODE_TYPE_REFERENCE: Record<DiagramType, string> = {
  bpmn: `- activity: Task or action step
- subprocess: Collapsed subprocess
- start-event: Process start
- end-event: Process end
- event-timer: Timer event
- event-message: Message event
- gateway-exclusive: XOR decision (one path)
- gateway-parallel: AND split/join (all paths)
- gateway-inclusive: OR split/join (one or more)
- data-object: Data artifact
- annotation: Text note`,
  er: `- entity: Database table (use columns[] for attributes)`,
  orgchart: `- person: Team member or role`,
  architecture: `- service: Application or microservice
- database: Data store
- queue: Message queue or event bus
- loadbalancer: Load balancer or API gateway
- external: External system`,
  sequence: `- participant: Actor or system (set lifeline: true)
- fragment: Combined fragment (loop, alt, opt)`,
  flowchart: `- process: Action or operation
- decision: Yes/no branch
- terminal: Start or end point
- io: Input/output step
- subprocess: Nested process`,
};

const EDGE_TYPE_REFERENCE: Record<DiagramType, string> = {
  bpmn: `- sequence (default): Flow between activities
- message: Cross-pool message flow
- association: Link to annotations/data`,
  er: `- (default): Relationship. Set cardinality: "1:1", "1:N", "N:1", or "M:N"`,
  orgchart: `- (default): Reports-to relationship`,
  architecture: `- (default): Connection. Use label for protocol/description`,
  sequence: `- sync (default): Synchronous call (solid arrow)
- async: Asynchronous message (open arrow)
- return: Response (dashed arrow)`,
  flowchart: `- (default): Flow. Use label for conditions ("Yes", "No")`,
};

export const TYPE_INFERENCE_RULES = `If the diagram type is not established, infer from the user's description:
- Business processes, workflows, approvals, order handling → bpmn
- Database schemas, tables, entities, data models → er
- Team structures, org hierarchies, reporting lines → orgchart
- System design, microservices, infrastructure, APIs → architecture
- Interactions between actors over time, API calls, request/response → sequence
- Decision logic, algorithms, if/else flows → flowchart`;

const DIAGRAM_EXAMPLES: Record<DiagramType, string> = {
  bpmn: `{"meta":{"diagramType":"bpmn","title":"Order Process","layoutDirection":"RIGHT"},"nodes":[{"id":"n1","type":"start-event","label":"Order received"},{"id":"n2","type":"activity","label":"Validate order"},{"id":"n3","type":"gateway-exclusive","label":"Valid?"},{"id":"n4","type":"activity","label":"Process payment"},{"id":"n5","type":"end-event","label":"Complete"}],"edges":[{"id":"e1","from":"n1","to":"n2"},{"id":"e2","from":"n2","to":"n3"},{"id":"e3","from":"n3","to":"n4","label":"Yes"},{"id":"e4","from":"n4","to":"n5"}]}`,
  er: `{"meta":{"diagramType":"er","title":"Blog Schema"},"nodes":[{"id":"n1","type":"entity","label":"User","columns":[{"name":"id","type":"uuid","isPrimaryKey":true},{"name":"email","type":"varchar","isUnique":true}]},{"id":"n2","type":"entity","label":"Post","columns":[{"name":"id","type":"uuid","isPrimaryKey":true},{"name":"author_id","type":"uuid","isForeignKey":true,"references":"User.id"},{"name":"title","type":"varchar"}]}],"edges":[{"id":"e1","from":"n1","to":"n2","cardinality":"1:N","label":"writes"}]}`,
  orgchart: `{"meta":{"diagramType":"orgchart","title":"Engineering Team","layoutDirection":"DOWN"},"nodes":[{"id":"n1","type":"person","label":"VP Engineering"},{"id":"n2","type":"person","label":"Frontend Lead"},{"id":"n3","type":"person","label":"Backend Lead"}],"edges":[{"id":"e1","from":"n1","to":"n2"},{"id":"e2","from":"n1","to":"n3"}]}`,
  architecture: `{"meta":{"diagramType":"architecture","title":"Web Stack"},"nodes":[{"id":"n1","type":"loadbalancer","label":"API Gateway"},{"id":"n2","type":"service","label":"Auth Service"},{"id":"n3","type":"service","label":"User Service"},{"id":"n4","type":"database","label":"PostgreSQL"}],"edges":[{"id":"e1","from":"n1","to":"n2","label":"REST"},{"id":"e2","from":"n1","to":"n3","label":"REST"},{"id":"e3","from":"n2","to":"n4"},{"id":"e4","from":"n3","to":"n4"}]}`,
  sequence: `{"meta":{"diagramType":"sequence","title":"Login Flow"},"nodes":[{"id":"n1","type":"participant","label":"Browser","lifeline":true},{"id":"n2","type":"participant","label":"Auth API","lifeline":true},{"id":"n3","type":"participant","label":"Database","lifeline":true}],"edges":[{"id":"e1","from":"n1","to":"n2","type":"sync","label":"POST /login"},{"id":"e2","from":"n2","to":"n3","type":"sync","label":"SELECT user"},{"id":"e3","from":"n3","to":"n2","type":"return","label":"user record"},{"id":"e4","from":"n2","to":"n1","type":"return","label":"JWT token"}]}`,
  flowchart: `{"meta":{"diagramType":"flowchart","title":"Validation Flow","layoutDirection":"DOWN"},"nodes":[{"id":"n1","type":"terminal","label":"Start"},{"id":"n2","type":"io","label":"Read input"},{"id":"n3","type":"decision","label":"Valid?"},{"id":"n4","type":"process","label":"Process data"},{"id":"n5","type":"terminal","label":"End"}],"edges":[{"id":"e1","from":"n1","to":"n2"},{"id":"e2","from":"n2","to":"n3"},{"id":"e3","from":"n3","to":"n4","label":"Yes"},{"id":"e4","from":"n3","to":"n2","label":"No"},{"id":"e5","from":"n4","to":"n5"}]}`,
};

const MAX_NEIGHBOR_NODES = 10;

/** Extract selected nodes + their connected edges from the full graph context */
export function buildSelectedContext(
  selectedElements: SelectedElement[],
  graphContext?: string,
): string {
  if (!graphContext) {
    return JSON.stringify({ selectedNodes: selectedElements });
  }

  try {
    const graph = JSON.parse(graphContext) as {
      nodes?: Array<{ id: string; [key: string]: unknown }>;
      edges?: Array<{ id: string; from: string; to: string; [key: string]: unknown }>;
    };
    const selectedIds = new Set(selectedElements.map((e) => e.id));

    const selectedNodes = (graph.nodes ?? []).filter((n) => selectedIds.has(n.id));
    const connectedEdges = (graph.edges ?? []).filter(
      (e) => selectedIds.has(e.from) || selectedIds.has(e.to),
    );

    // 1-hop neighbor IDs (nodes connected to selected nodes but not selected themselves)
    const neighborIds = new Set<string>();
    for (const edge of connectedEdges) {
      if (!selectedIds.has(edge.from)) neighborIds.add(edge.from);
      if (!selectedIds.has(edge.to)) neighborIds.add(edge.to);
    }
    const neighborNodes = (graph.nodes ?? [])
      .filter((n) => neighborIds.has(n.id))
      .slice(0, MAX_NEIGHBOR_NODES)
      .map((n) => ({ id: n.id, label: (n as { label?: string }).label ?? n.id }));

    return JSON.stringify({ selectedNodes, connectedEdges, neighborNodes });
  } catch {
    return JSON.stringify({ selectedNodes: selectedElements });
  }
}

function buildScopedContextSection(
  selectedElements?: SelectedElement[],
  selectedContext?: string,
): string {
  if (!selectedElements || selectedElements.length === 0) return "";

  const elementList = selectedElements
    .map((e) => `- **${e.label}** (${e.type})`)
    .join("\n");

  return `

## Scoped context — targeted modification
The user has selected ${selectedElements.length} element(s) for modification:
${elementList}
${selectedContext ? `\nSelected element details:\n\`\`\`json\n${selectedContext}\n\`\`\`` : ""}

**IMPORTANT:** The user wants to modify ONLY these elements. When using the \`generateDiagram\` tool:
- Include ALL nodes and edges in your output (the tool replaces the entire graph)
- Focus changes on the selected elements and their immediate connections
- Preserve all other nodes and edges unchanged
- Prefer minimal changes: modify, split, merge, or restructure only the referenced elements`;
}

export function buildCopilotSystemPrompt(
  diagramType: DiagramType,
  options?: {
    graphContext?: string;
    selectedElements?: SelectedElement[];
    selectedContext?: string;
  },
): string {
  const description = DIAGRAM_DESCRIPTIONS[diagramType];
  const graphContext = options?.graphContext;
  const selectedElements = options?.selectedElements;
  const selectedContext = options?.selectedContext;
  const date = new Date().toLocaleDateString("en-US", {
    year: "numeric",
    month: "short",
    day: "2-digit",
    weekday: "short",
  });

  const typeSpecificSections: string[] = [];

  if (diagramType === "er") {
    typeSpecificSections.push(`### E-R column format
Each entity node has a \`columns\` array: [{ name, type, isPrimaryKey?, isForeignKey?, isNullable?, isUnique?, references? }]`);
  }

  if (diagramType === "bpmn") {
    typeSpecificSections.push(`### BPMN pools and lanes
Use \`pools\` to define swim lanes. Assign nodes to lanes via the \`lane\` field matching a lane ID.`);
  }

  return `You are the domaingraph AI copilot — a diagram design assistant that can discuss, generate, and modify diagrams.

## Current diagram
Type: ${diagramType.toUpperCase()} — ${description}

## Capabilities
- Answer questions about diagram design and best practices
- Generate complete diagrams from natural language descriptions
- Modify existing diagrams based on user requests
- Analyze the current diagram and suggest improvements

## Generating and modifying diagrams
When the user describes what they want to build, asks you to create a diagram, or requests changes, use the \`generateDiagram\` tool.

1. Briefly explain what you'll create or change (1-2 sentences)
2. Call the \`generateDiagram\` tool with the complete GraphData object

When modifying an existing diagram, include ALL nodes and edges in the output (not just changes). The tool output replaces the entire graph.

### GraphData format
The tool accepts:
- \`meta\`: { diagramType, title, description?, layoutDirection?: "DOWN"|"RIGHT"|"LEFT"|"UP" }
- \`nodes\`: [{ id, type, label, tag?, columns?, lane?, group?, lifeline? }]
- \`edges\`: [{ id, from, to, label?, type?, cardinality? }]
- \`pools\`: (BPMN only) [{ id, label, lanes: [{ id, label }] }]
- \`groups\`: [{ id, label, color? }]

### Node types for ${diagramType.toUpperCase()}
${NODE_TYPE_REFERENCE[diagramType]}

### Edge types for ${diagramType.toUpperCase()}
${EDGE_TYPE_REFERENCE[diagramType]}

${typeSpecificSections.length > 0 ? typeSpecificSections.join("\n\n") + "\n" : ""}## Example
\`\`\`json
${DIAGRAM_EXAMPLES[diagramType]}
\`\`\`

## Type inference
${TYPE_INFERENCE_RULES}

## Current graph state
${graphContext ? `The diagram currently contains:\n\`\`\`json\n${graphContext}\n\`\`\`` : "Empty canvas — no nodes or edges yet."}

## Constraints
- Maximum 200 nodes per generation
- Generate IDs as "n1", "n2", ... for nodes and "e1", "e2", ... for edges
- Keep text responses concise and diagram-focused
- Use markdown formatting (bold, lists, code blocks) — no h1 headings
- Today's date is ${date}

## Change summary
When modifying an existing diagram, include a brief change summary in your response before calling the tool. Format: "**Changes:** Adding N nodes, modifying N edges, removing N nodes." This helps users understand what will change before reviewing the visual diff.

## Semantic analysis
After generating or modifying a diagram, briefly note any semantic issues you detect:
${SEMANTIC_RULES[diagramType]}
Present as helpful inline suggestions using "Note:" or "Consider:" prefix.
Do not block diagram generation for semantic issues.${buildScopedContextSection(selectedElements, selectedContext)}`;
}
