// arcrun cypher-executor 型別定義

// Service Binding 型別（CF Workers 直接呼叫，不走公網）
export type ServiceBinding = {
  fetch(request: Request): Promise<Response>;
};

export type Bindings = {
  // Logic component Service Bindings
  SVC_IF_CONTROL: ServiceBinding;
  SVC_SWITCH: ServiceBinding;
  SVC_FOREACH_CONTROL: ServiceBinding;
  SVC_FILTER: ServiceBinding;
  SVC_MERGE: ServiceBinding;
  SVC_TRY_CATCH: ServiceBinding;
  SVC_WAIT: ServiceBinding;
  SVC_SET: ServiceBinding;
  SVC_ARRAY_OPS: ServiceBinding;
  SVC_STRING_OPS: ServiceBinding;
  SVC_NUMBER_OPS: ServiceBinding;
  SVC_DATE_OPS: ServiceBinding;
  SVC_VALIDATE_JSON: ServiceBinding;
  // SVC_AI_TRANSFORM_* 已移除（Phase 2 刪 ai_transform 零件 + wrangler.toml service binding）
  // KV Context Store：節點 output 透過 KV 傳遞，解決同名欄位衝突
  EXEC_CONTEXT: KVNamespace;
  // Recipe Store：API recipe 定義（key: recipe:{canonical_id} 或 idx:{hash_id}）
  RECIPES: KVNamespace;
  // Webhook Store：key = workflow name，value = Workflow JSON
  WEBHOOKS: KVNamespace;
  // Credential Store：AES-GCM 加密存放用戶 API token（舊家；credential-store-migration T7
  // 雙讀過渡期間仍是 fallback 讀路徑，本次 T5 只改「新寫入」，不動這裡）
  CREDENTIALS_KV: KVNamespace;
  // ⚠️ D38 圍牆修復（2026-08-07）後零讀寫點：credential 目錄已改走 KBDB entries HTTP API
  // （見 cypher-executor/src/routes/credentials.ts），不再對這顆 D1 下任何 SQL。binding
  // 因 wrangler.toml 被權限鎖住（D38 決策所述）暫留宣告，比照 ANALYTICS_KV 同一模式
  // （commit 60688c3：binding 留在 toml，程式碼零讀寫點）。舊表資料遷移路徑見
  // kbdb/migrations/0006_drop_credentials_table.sql。
  CREDENTIALS_DB: D1Database;
  // Analytics：執行統計（fire-and-forget，key = stats:{workflowId}:{timestamp}）
  ANALYTICS_KV: KVNamespace;
  // Users：OAuth 登入用戶帳號（key = user:{provider}:{provider_id}）
  USERS_KV: KVNamespace;
  // Sessions：登入 session（key = sess:{session_id}，TTL 7d）
  SESSIONS_KV: KVNamespace;
  // Workers AI
  AI: Ai;
  // 環境變數
  ENVIRONMENT: string;
  MULTI_TENANT?: string;    // "false" = Self-hosted 單租戶模式，預設 "true"
  // OAuth Secrets（wrangler secret）
  GOOGLE_CLIENT_ID?: string;
  GOOGLE_CLIENT_SECRET?: string;
  GITHUB_CLIENT_ID?: string;
  GITHUB_CLIENT_SECRET?: string;
  SESSION_SIGNING_SECRET?: string;  // 用於 HMAC session ID（可選，也可直接用 UUID）
  // KBDB 整合
  KBDB_INTERNAL_TOKEN?: string;
  // credential-store-migration T3/T5：能打 CF Workers Scripts secrets 管理 API 的 token
  // （wrangler secret；PUT/DELETE .../workers/scripts/{script}/secrets，唯寫讀不回值，
  // D19 語意仍成立）+ 對應帳號 id（非機密，比照 WORKER_SUBDOMAIN 由 deploy.ts 自動注入）。
  // 缺其一 → /credentials 寫入路徑誠實回報未就緒（不假綠），見 T3。
  CF_SECRETS_API_TOKEN?: string;
  CF_ACCOUNT_ID?: string;
  // KBDB Base worker URL（recipe 成功記錄 /recipe-stats/record、fragment 抓取）。
  // 未設 fallback 見各使用點（recipe-expander 預設 kbdb.finally.click）。kbdb-base SDD §7.1。
  KBDB_BASE_URL?: string;
  // Component Worker subdomain（workers.dev 帳號 subdomain）
  // 必填：cypher-executor 用此組出 component worker URL（避開同 zone 自循環死鎖，見 P0 #9）
  // self-hosted fork 必須改 wrangler.toml [vars] 為自己的帳號 subdomain
  WORKER_SUBDOMAIN: string;
  /**
   * t162：本實例安裝時的 bundle 版本（格式 `YYYY-MM-DD+<commit7>`）。
   * 由安裝器 deployBundledWorker 注入（worker.js:805），**給 daemon 比對用**——
   * daemon `/health` 讀不到就恆判「需要更新」（假警報迴圈，leo 07-31 實撞）。
   * 未注入（本地 dev／舊實例）＝ undefined，/health 省略該欄。
   */
  ARCRUN_BUNDLE_VERSION?: string;
  /**
   * Arcrun#106：這份成品實際來自哪個 commit（40 碼 sha）。
   * `ARCRUN_BUNDLE_VERSION` 是**發行頻道的編號**（semver，Portal/daemon 拿它比新舊），
   * 這個是**真的部了哪份碼**——兩個一起吐，標籤跟成品漂掉時查得出來。
   * 由 `acr init/update`（cli/src/lib/deploy.ts）注入；安裝器那條路沒有此 var → /health 省略該欄。
   */
  ARCRUN_BUNDLE_COMMIT?: string;
  // Platform telemetry api_key（可選，wrangler secret）
  // 對應 SDD .agents/specs/llm-interface/ M1.2
  // 設了會把 agent-telemetry block 都聚集在 platform_telemetry user_id 下
  // 沒設就 fallback 到當下用戶的 ak_，會寫進該用戶 KBDB namespace（次優但能用）
  PLATFORM_API_KEY?: string;
  // Console 固定租戶字串（Arcrun#3 發現②，非機密——self-hosted 架構本就是明碼 namespace）。
  // console 登入後端一律用這個字串打 /kbdb/*、/workflows/search（不做多租戶，登入系統只擋外人看頁面）。
  // 未設 → routes/console-auth.ts 預設 "leo"（發現①已核實：D1 458,357 筆資料實際使用的租戶字串）。
  CONSOLE_TENANT?: string;
  /**
   * 這台實例的**知識命名空間**（Arcrun#108）＝使用者 `~/.arcrun/config.yaml` 的 `api_key`。
   *
   * 由 `acr init/update`（cli/src/lib/deploy.ts CLI_MANAGED_VARS）自動注入，**使用者不必手動維護**：
   * 它就是 CLI push workflow（`{ns}:wf:*`）、小幫手上傳知識（`owner_id=ns`）、MCP Bearer
   * 用的同一個值 ⇒ 讀取端用它過濾，永遠對得上寫入端。
   *
   * 為什麼不沿用 `CONSOLE_TENANT`：那是 repo toml 帶的**官方 prod 值**（`leo`），
   * self-hosted 實例的資料根本不在它底下（#108 實撞：1854 條被過濾成 0），
   * 而且 `CONSOLE_TENANT` 同時還是帳號子 namespace（`{tenant}::portal`）的組成，
   * 改它會讓舊實例登不進去。兩件事拆成兩個 var，各自對應各自的真相源。
   * 解析邏輯只在 `src/lib/tenant.ts`（唯一產地，機械閘看守）。
   */
  ARCRUN_NAMESPACE?: string;
  // Console 顯示品牌／實例名（Arcrun#21 rebrand，非機密）。只影響 UI 字樣（title/header/logo），
  // 不影響任何行為。未設 → "Arcrun"（console 是引擎共用件，不寫死產品名）。
  // 實例可覆蓋，例：arcrun-rag demo 可設 --var CONSOLE_BRAND:"Arcrun RAG"。
  CONSOLE_BRAND?: string;
  // Console 頁面組成 profile（console-profile-trim，非機密）。只影響 UI 組成與落地頁，
  // 不動任何 API/資料行為。未設或 "full" → 全套（Mira 實例現行為，一字不變）。
  // "rag" → 企業產品樣：側欄只留 總庫搜尋/工作流/設定（含卡片詳頁），落地＝搜尋頁；
  // 駕駛艙/分流台/憑證管理隱藏且路由導回搜尋；/console/dashboard 302 回 /console。
  // 例：arcrun-rag demo 可設 --var CONSOLE_PROFILE:rag。
  CONSOLE_PROFILE?: string;
  // ── console 駕駛艙「等你的事」活資料源（fix/console-dashboard-live-data，2026-07-07）──
  // 等 leo 的事真相源＝InkStoneCo sprint 檔「## 等 leo 清單」（progress-guard 每日維護），
  // 不是沒人維護的 KBDB dash_wait。cypher 進程內 fetch Gitea API（自架 Gitea，非 GitHub，
  // 無 D20 頻率疑慮）。缺 GITEA_BASE_URL 或 GITEA_TOKEN → 該區塊 fallback dash_wait
  // （頁面誠實標 age + stale），不假綠。
  GITEA_BASE_URL?: string; // wrangler.toml [vars]，如 https://git.uncle6.me
  GITEA_TOKEN?: string; // wrangler secret（建議唯讀 scope token）
  GITEA_SPRINT_REPO?: string; // 預設 Leo/InkStoneCo
  GITEA_SPRINT_DIR?: string; // 預設 system-dev/docs/3-specs/autonomy-dispatch
  // （ARCRUN_BUNDLE_VERSION 原本在這裡重複宣告了一次——TS2300 重複識別字，
  //   #106 順手併回上面那一處，說明同源，行為零變化。）
  // MCP access_token 存活秒數的「顯示鏡像」（console 設定頁 MCP TTL 佔位區塊用）。
  // 真相住在 mcp worker 的同名 env（mcp/src/types.ts，預設 2592000＝30 天）；cypher 這份
  // 只供顯示，兩處部署時要一致（#32 形態 config 同步教訓）。未設 → 頁面如實標「預設值」。
  // 可調功能（設定頁改→存 KBDB→發 token 時讀）＝Arcrun#19，實作前先在部署端 env 調。
  MCP_TOKEN_TTL?: string;
  // Portal session TTL 秒數（portal-auth P2，#24/#25，非機密）。portal_sess:{token} KV 的
  // expirationTtl。未設 → 604800（7 天，design §4.3——issue 要求短效，比 console 30 天緊）。
  // 只影響新發的 session；權限/停用的即時性不靠 TTL（每請求回讀 user record）。
  PORTAL_SESSION_TTL?: string;
  // Portal / console 前端站的 origin 白名單（逗號分隔，非機密）。index.ts 的 CORS 讀它；
  // D62 的「修改密碼」連結也用它當「使用者會看到的那個網址」（未設 → 用 workers.dev 兄弟位址推導）。
  UI_ORIGINS?: string;
  // ── D62「忘記密碼」＝寄一條「修改密碼」連結（非機密）───────────────────────────
  // 中央代寄服務的 base URL（landing worker）。**用戶自己的實例沒有寄信能力**——安裝器
  // 部署 cypher 的 binding 只有 ai/d1/kv/plain_text/secret_text/service/vectorize，
  // **沒有 send_email**；能寄信的是我們 landing 的 CF Email Service（寄件網域 arcrun.dev）。
  // 未設 → /portal/password/forgot 誠實回 503 `mail_relay_not_configured`，不假裝寄出去了。
  // ⚠️ 「由中央代寄」是依 leo「寄給你」推導的**假設**，尚待他正式表態（D62 未裁前置）。
  PORTAL_MAIL_RELAY_BASE?: string;
  // 代寄服務的共享秘密（可選）。設了就在代寄請求帶 X-Arcrun-Relay-Key，讓 landing 端
  // 分辨「這是我們自己的實例」。未設＝不帶（landing 端仍有速率限制與固定樣板）。
  PORTAL_MAIL_RELAY_KEY?: string;
  // Portal 工作流頁可見性（portal-auth P3，design D-8 定案，非機密）：admin（預設）/ all / off。
  // 路由層 enforce 在 /portal/data/workflows（無權 403、off 404），前端只照 /portal/session
  // 的 workflows_visible 顯示或隱藏 nav 項。壞值退回 admin（不因 typo 意外全開）。
  PORTAL_SHOW_WORKFLOWS?: string;
  // ── Portal 上傳（portal-demo-suite，三者齊全才啟用；任一缺 → /portal/data/upload 404
  // 「未啟用上傳」且前端 nav 不顯示——Mira 零影響：未設＝功能不存在，行為一字不變）──
  // 目標 Gitea repo（owner/repo，如 "Leo/arcrun-rag-demo-knowledge"）。上傳落 docs/{filename}。
  PORTAL_UPLOAD_REPO?: string;
  // Gitea base URL（如 https://git.uncle6.me）。刻意不共用 GITEA_BASE_URL（那是駕駛艙 sprint
  // 資料源、建議唯讀 token）——上傳要寫入權限，租戶/scope 都不同，混用會互相牽制。
  PORTAL_UPLOAD_GITEA?: string;
  // 能寫該 repo contents API 的 token（wrangler secret）。**只在 server 側，永不下發前端**。
  PORTAL_UPLOAD_TOKEN?: string;
  // Portal 來源回溯連結 web base（可選，非機密）。設了之後 gitea:// 開頭的 source 在 Portal
  // 顯示成可點超連結：gitea://<path>#<chunk> → {base}/<path>（chunk 錨點捨棄——Gitea 網頁
  // 無 chunk 概念）。例："https://git.uncle6.me/Leo/arcrun-rag-demo-knowledge/src/branch/main"。
  // 未設 → 純文字顯示，行為與現狀一字不變。知識庫 repo 是 private 時點了會要登入——要不要
  // 設由實例自己決定（demo 知識庫是 public，適用）。
  PORTAL_SOURCE_WEB_BASE?: string;
  // 零件 registry worker base URL（可選，非機密）。未設 → 用 WORKER_SUBDOMAIN 現算
  // https://arcrun-registry.<subdomain>.workers.dev（wasmWorkerUrl 慣例）。
  // 本地 wrangler dev／self-hosted 把 registry 掛別處時覆蓋（/cypher/search 存在性查詢用）。
  REGISTRY_BASE_URL?: string;
  // kbdb-graph-plugin worker base URL（可選）。未設 → 用 WORKER_SUBDOMAIN 現算
  // https://kbdb-graph-plugin.<subdomain>.workers.dev（該 repo wrangler.toml name 固定）。
  // console 卡片詳頁「關聯視圖」經 cypher proxy 打它（kbdb-proxy.ts /kbdb/graph/neighbors/:name）。
  KBDB_GRAPH_URL?: string;
};

// 重新 export Cloudflare Workers ExecutionContext 以便其他 module 用
export type { ExecutionContext } from '@cloudflare/workers-types';

// 圖結構定義
export type GraphNode = {
  id: string;
  type: 'Input' | 'Component' | 'Output';
  componentId?: string;
  data?: Record<string, unknown>;
};

export type EdgeType =
  | 'PIPE' | 'IF' | 'FOREACH' | 'CONTINUE'          // 現有
  | 'IS_A' | 'ON_SUCCESS' | 'ON_FAIL'                // 執行語意
  | 'ON_TRUE' | 'ON_FALSE' | 'ON_BRANCH'             // 條件語意（SDD workflow-discovery 3.11）
  | 'ON_CLICK' | 'CALLS_SUBFLOW'                     // 觸發語意
  | 'CONTAINS' | 'HAS_STYLE' | 'HAS_BEHAVIOR';       // 結構語意（記錄圖結構，不執行）

export type GraphEdge = {
  from: string;
  to: string;
  type: EdgeType;
  condition?: string;  // IF 的條件表達式
  iterator?: string;   // FOREACH 的迭代變數名
  /** ON_BRANCH 的具名分支（對應 switch 零件 output 的 data.branch） */
  branch?: string;
};

export type ExecutionGraph = {
  id: string;
  name: string;
  nodes: GraphNode[];
  edges: GraphEdge[];
};

// 執行結果
export type ExecutionResult = {
  success: boolean;
  data: unknown;
  trace: TraceStep[];
  duration_ms: number;
};

export type TraceStep = {
  nodeId: string;
  type: string;
  input: unknown;
  output: unknown;
  duration_ms: number;
  error?: string;
};

// 零件執行器介面（直接可執行函數，不用動態 eval）
export type ComponentRunner = (context: unknown) => unknown | Promise<unknown>;

// KV Context Store（BUILD-006）：節點 output 命名空間前綴
// KV key 格式：{run_id}:node:{node_id}  value 是節點 output 的 JSON 字串
// TTL = 3600 秒（1 小時），執行後自動清除
export type KVContextStore = {
  runId: string;
  kv: KVNamespace;
};

/** 從 KV 讀取節點 output（不存在時回傳 undefined）*/
export async function kvGetNodeOutput(store: KVContextStore, nodeId: string): Promise<Record<string, unknown> | undefined> {
  try {
    const val = await store.kv.get(`${store.runId}:node:${nodeId}`, 'json');
    return val as Record<string, unknown> | undefined;
  } catch {
    return undefined;
  }
}

/**
 * Workflow 暫停（resumable workflow）：
 * 節點回 pending → graph-executor 持久化 state + throw 此類，被頂層接住回 paused 狀態給 caller
 * SDD: matrix/arcrun/.agents/specs/resumable-workflow/design.md
 */
export class WorkflowPaused extends Error {
  readonly task_id: string;
  readonly run_id: string;
  readonly paused_node_id: string;
  readonly trace_so_far: TraceStep[];

  constructor(task_id: string, run_id: string, paused_node_id: string, trace_so_far: TraceStep[]) {
    super(`workflow paused at node ${paused_node_id} waiting for task ${task_id}`);
    this.name = 'WorkflowPaused';
    this.task_id = task_id;
    this.run_id = run_id;
    this.paused_node_id = paused_node_id;
    this.trace_so_far = trace_so_far;
  }
}

/** 執行失敗時拋出的自訂 Error，攜帶完整 trace 與失敗節點資訊 */
export class ExecutionError extends Error {
  readonly failed_node: string;
  readonly failed_input: unknown;
  readonly trace: TraceStep[];

  constructor(
    message: string,
    failed_node: string,
    failed_input: unknown,
    trace: TraceStep[],
  ) {
    super(message);
    this.name = 'ExecutionError';
    this.failed_node = failed_node;
    this.failed_input = failed_input;
    this.trace = trace;
  }
}

/** 將節點 output 寫入 KV（TTL 1 小時）*/
export async function kvSetNodeOutput(store: KVContextStore, nodeId: string, output: unknown): Promise<void> {
  try {
    await store.kv.put(
      `${store.runId}:node:${nodeId}`,
      JSON.stringify(output),
      { expirationTtl: 3600 },
    );
  } catch {
    // KV 寫入失敗不影響執行（fallback 到記憶體 merge）
  }
}
