import { SEMANTIC_EDGE_MAP, VALID_EDGE_TYPES } from '../lib/constants';
import type { EdgeType } from '../types';

export type ParsedTriplets = {
  edges: Array<{ from: string; to: string; label: string }>;
  nodeNames: Set<string>;
  /** 出現在 from 但不出現在任何 to 的節點（事件源 / 起始點） */
  sourceNodes: Set<string>;
  /** 出現在 to 但不出現在任何 from 的節點（終點）*/
  sinkNodes: Set<string>;
};

export type NodeRole = 'Input' | 'Component' | 'Output';

/**
 * 解析後的零件 URI
 * 支援格式：
 *   component://validate_json
 *   component://validate_json@stable
 *   component://validate_json@pinned:v1
 *   workflow://wf_save_to_db
 *   ui://u6u-btn
 *   style://glow-effect
 */
export interface ResolvedComponentId {
  type: 'component' | 'workflow' | 'ui' | 'style';
  canonicalId: string;
  stability: 'floating' | 'stable' | 'pinned';
  pinnedVersion?: string;
  /** 原始 URI 字串 */
  raw: string;
}

/** 解析零件 URI 協議 */
export function resolveComponentId(uri: string): ResolvedComponentId {
  const raw = uri.trim();

  // 解析協議前綴
  let type: ResolvedComponentId['type'] = 'component';
  let rest = raw;

  if (raw.startsWith('component://')) {
    type = 'component';
    rest = raw.slice('component://'.length);
  } else if (raw.startsWith('workflow://')) {
    type = 'workflow';
    rest = raw.slice('workflow://'.length);
  } else if (raw.startsWith('ui://')) {
    type = 'ui';
    rest = raw.slice('ui://'.length);
  } else if (raw.startsWith('style://')) {
    type = 'style';
    rest = raw.slice('style://'.length);
  }

  // 解析穩定性標籤
  // component://id@stable
  // component://id@pinned:v1
  let canonicalId = rest;
  let stability: ResolvedComponentId['stability'] = 'floating';
  let pinnedVersion: string | undefined;

  const atIdx = rest.indexOf('@');
  if (atIdx > 0) {
    canonicalId = rest.slice(0, atIdx);
    const tag = rest.slice(atIdx + 1);
    if (tag === 'stable') {
      stability = 'stable';
    } else if (tag.startsWith('pinned:')) {
      stability = 'pinned';
      pinnedVersion = tag.slice('pinned:'.length);
    }
  }

  return { type, canonicalId, stability, pinnedVersion, raw };
}

/** 解析 triplets 字串陣列，回傳節點與邊的結構 */
export function parseTriplets(rawTriplets: unknown[]): ParsedTriplets | null {
  const edges: Array<{ from: string; to: string; label: string }> = [];
  const nodeNames = new Set<string>();
  const fromSet = new Set<string>();
  const toSet = new Set<string>();

  for (const line of rawTriplets) {
    if (typeof line !== 'string') continue;
    const parts = line.split('>>').map((s: string) => s.trim());
    if (parts.length !== 3) continue;
    const [from, action, to] = parts;
    edges.push({ from, to, label: action });
    nodeNames.add(from);
    nodeNames.add(to);
    fromSet.add(from);
    toSet.add(to);
  }

  if (nodeNames.size === 0) return null;

  const sourceNodes = new Set([...fromSet].filter(n => !toSet.has(n)));
  const sinkNodes = new Set([...toSet].filter(n => !fromSet.has(n)));
  return { edges, nodeNames, sourceNodes, sinkNodes };
}

/** 保留字節點名稱 — 明確宣告為 Input 或 Output 端點 */
const INPUT_NAMES = new Set(['input', 'trigger', 'webhook', 'start']);
const OUTPUT_NAMES = new Set(['output', 'result', 'end', 'done']);

/**
 * 是否為「虛擬 IO 節點名」（input/output 這類非零件的佔位節點）。
 * searchNodes 用它決定存在性查詢的短路：**只有字面上是虛擬 IO 名**才免查——
 * 位置上是頭節點但名字是真零件（例 `aes_encrypt >> ON_SUCCESS >> code` 的頭）
 * 仍要查兩庫，否則缺件被角色掩蓋、又回到「假 found」（task 3.7 實測踩到）。
 */
export function isVirtualIoName(name: string): boolean {
  const lower = name.toLowerCase();
  return INPUT_NAMES.has(lower) || OUTPUT_NAMES.has(lower);
}

/** 根據節點在圖中的位置決定其 type
 *
 *  規則：
 *  - 名稱在 INPUT_NAMES  → Input（無論位置）
 *  - 名稱在 OUTPUT_NAMES → Output（無論位置）
 *  - sourceNode（只出現在 from）且名稱不在 INPUT_NAMES → Component（例如 cron 作為觸發源）
 *  - sinkNode（只出現在 to）且名稱不在 OUTPUT_NAMES → Component（最常見情況：最後一個實際零件）
 *  - 其他中間節點 → Component
 */
export function resolveNodeRole(name: string, parsed: ParsedTriplets): NodeRole {
  if (INPUT_NAMES.has(name.toLowerCase())) return 'Input';
  if (OUTPUT_NAMES.has(name.toLowerCase())) return 'Output';
  if (parsed.sourceNodes.has(name)) return 'Input';
  return 'Component';
}

/** 將 edge label 轉換為合法 EdgeType
 *  優先序：VALID_EDGE_TYPES（完整匹配）→ SEMANTIC_EDGE_MAP（語意別名）→ 預設 PIPE */
export function toEdgeType(label: string): EdgeType {
  const upper = label.toUpperCase();
  if (VALID_EDGE_TYPES.has(upper)) return upper as EdgeType;
  return (SEMANTIC_EDGE_MAP[label] ?? SEMANTIC_EDGE_MAP[upper] ?? 'PIPE') as EdgeType;
}
