/**
 * Recipe expander：把 prompt_recipe 展開成 claude_api 的實際 input
 * SDD: matrix/arcrun/.agents/specs/recipe-system/design.md §2.2 + Phase 2.1
 *
 * 流程：
 *   1. loadPromptRecipe 取定義
 *   2. fragments → 用 KBDB API 抓 block content
 *   3. inputs → 從 workflow context 取值 + 跑 transform
 *   4. 套進 prompt_assembly.system / .user 的 {{var}} 模板
 *   5. 回傳 { prompt, model, output_format, output_required_fields }
 */

import { loadPromptRecipe, RecipeLoadError } from './recipe-loader';
import { applyTransform } from './recipe-transforms';
import type { Fragment, RecipeInput } from './prompt-recipe-schema';

type ExpanderEnv = {
  RECIPES: { get: (key: string) => Promise<string | null> };
  KBDB_BASE_URL?: string;
};

export interface ExpandedRecipe {
  prompt: string;          // user prompt（system + user 用 \n\n--- system ---\n 分隔）
  model: 'haiku' | 'sonnet' | 'opus';
  output_format: 'text' | 'json';
  output_required_fields?: string[];
}

/** 從 path 取嵌套值，例如 "ctx.read_drafts.blocks" / "loop.item" */
function getByPath(ctx: Record<string, unknown>, path: string): unknown {
  const parts = path.split('.');
  let cur: unknown = ctx;
  for (const p of parts) {
    if (cur === null || cur === undefined) return undefined;
    if (typeof cur !== 'object') return undefined;
    cur = (cur as Record<string, unknown>)[p];
  }
  return cur;
}

/** {{var}} 模板替換（top-level vars 物件） */
function interpolate(template: string, vars: Record<string, string>): string {
  return template.replace(/\{\{(\w+)\}\}/g, (_, key) => (vars[key] !== undefined ? vars[key] : `{{${key}}}`));
}

async function fetchKbdbBlock(
  env: ExpanderEnv,
  apiKey: string,
  fragment: Extract<Fragment, { source: 'kbdb_block' }>,
): Promise<unknown> {
  const base = (env.KBDB_BASE_URL ?? 'https://kbdb.finally.click').replace(/\/$/, '');
  let url: string;
  if (fragment.block_id) {
    url = `${base}/blocks/${encodeURIComponent(fragment.block_id)}`;
  } else {
    url = `${base}/blocks?page_name=${encodeURIComponent(fragment.block_page_name!)}&limit=1`;
  }
  const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
  if (!res.ok) throw new Error(`KBDB fragment 抓取失敗 (${res.status}): ${url}`);
  const data = (await res.json()) as Record<string, unknown>;

  // page_name 模式回 {blocks:[]}，block_id 模式直接回 block 物件
  const block: Record<string, unknown> = fragment.block_id
    ? data
    : ((data.blocks as unknown[])?.[0] as Record<string, unknown>) ?? {};
  if (!block) throw new Error(`KBDB block 不存在: ${fragment.block_id ?? fragment.block_page_name}`);

  const fieldVal = block[fragment.field];
  if (fieldVal === undefined) throw new Error(`block 缺欄位 "${fragment.field}"`);
  return fieldVal;
}

async function resolveFragment(
  env: ExpanderEnv,
  apiKey: string,
  frag: Fragment,
): Promise<{ var: string; value: unknown }> {
  if (frag.source === 'kv') {
    const val = await env.RECIPES.get(frag.key);
    if (val === null) throw new Error(`KV 找不到 key: ${frag.key}`);
    return { var: frag.var, value: val };
  }
  return { var: frag.var, value: await fetchKbdbBlock(env, apiKey, frag) };
}

function resolveInput(input: RecipeInput, ctx: Record<string, unknown>): { var: string; value: unknown } {
  let val = getByPath(ctx, input.from);
  const beforeDefault = val;
  if (val === undefined) val = input.default;
  try {
    if (input.transform) val = applyTransform(val, input.transform);
    return { var: input.var, value: val };
  } catch (e) {
    // 把 path 跟原值放進錯誤訊息，方便 debug recipe
    const valType = Array.isArray(beforeDefault) ? `array(${beforeDefault.length})`
      : beforeDefault === undefined ? 'undefined(default applied)'
      : typeof beforeDefault;
    throw new Error(`${e instanceof Error ? e.message : String(e)} [path=${input.from}, type=${valType}]`);
  }
}

/** 主入口：展開 recipe → 組 prompt */
export async function expandPromptRecipe(
  recipeRef: string,
  ctx: Record<string, unknown>,
  env: ExpanderEnv,
  apiKey: string, // KBDB partner key（從 workflow auth 來）
): Promise<ExpandedRecipe> {
  const recipe = await loadPromptRecipe(recipeRef, env.RECIPES);

  const vars: Record<string, string> = {};

  for (const frag of recipe.fragments) {
    const { var: name, value } = await resolveFragment(env, apiKey, frag);
    vars[name] = typeof value === 'string' ? value : JSON.stringify(value);
  }
  for (const inp of recipe.inputs) {
    const { var: name, value } = resolveInput(inp, ctx);
    vars[name] = typeof value === 'string' ? value : JSON.stringify(value);
  }

  const system = interpolate(recipe.prompt_assembly.system, vars);
  const user = interpolate(recipe.prompt_assembly.user, vars);

  // claude_api 容器目前吃單一 prompt 字串 → system + user 用分隔線拼
  const prompt = `${system}\n\n--- USER ---\n\n${user}`;

  return {
    prompt,
    model: recipe.model,
    output_format: recipe.output.format,
    output_required_fields: recipe.output.required_fields,
  };
}

export { RecipeLoadError };
