// queryComponents — 查詢零件合約
// 支援兩種查詢 id：
//   component_hash_id（cmp_xxxxxxxx）— 永久穩定，workflow 引用用
//   canonical_id（小寫底線）         — 可讀名稱，透過 idx: 反查索引解析
// Requirements: 12.2, 12.3

import type { Bindings } from '../types';

export interface ComponentRecord {
  component_hash_id: string;
  canonical_id: string;
  display_name: string;
  version: string;
  category: string;
  stability: string;
  status: string;
  description: string;
  aliases: string[];
  tags: string[];
  success_rate: number;
  avg_duration_ms: number;
  call_count: number;
  wasm_r2_key?: string;
  score: number;
  // 零件合約的 I/O schema。KV 記錄一直有存（indexOnlyComponent 寫入），
  // 過去查詢層把它丟掉 ⇒ /cypher/search 給不出「怎麼填 payload」——2026-07-31 補透傳。
  input_schema?: Record<string, unknown>;
  output_schema?: Record<string, unknown>;
}

// ── id 解析：支援 hash_id 和 canonical_id 兩種格式 ──────────────────────────

async function resolveHashId(id: string, env: Bindings): Promise<string | null> {
  // 已經是 hash_id 格式
  if (id.startsWith('cmp_')) return id;
  // canonical_id → 透過 idx: 反查索引
  const hashId = await env.SUBMISSIONS_KV.get(`idx:${id}`);
  return hashId;
}

// ── 取得零件的所有版本 ────────────────────────────────────────────────────────

async function listVersions(hashId: string, env: Bindings): Promise<ComponentRecord[]> {
  const prefix = `comp:${hashId}:`;
  const list = await env.SUBMISSIONS_KV.list({ prefix });

  const records: ComponentRecord[] = [];
  for (const key of list.keys) {
    const raw = await env.SUBMISSIONS_KV.get(key.name);
    if (!raw) continue;
    try {
      const v = JSON.parse(raw);
      if (v.status === 'tombstone') continue;
      records.push(toComponentRecord(v));
    } catch {
      continue;
    }
  }
  return records;
}

// ── 公開 API ──────────────────────────────────────────────────────────────────

/** 取得零件最優版本（floating 策略：成功率 × 速度 × log(使用次數)） */
export async function getComponent(
  id: string,
  env: Bindings,
): Promise<ComponentRecord | null> {
  const hashId = await resolveHashId(id, env);
  if (!hashId) return null;

  const versions = await listVersions(hashId, env);
  if (versions.length === 0) return null;

  versions.sort((a, b) => b.score - a.score);
  return versions[0];
}

/** 取得零件所有版本清單（含評分排序） */
export async function getComponentVersions(
  id: string,
  env: Bindings,
): Promise<ComponentRecord[]> {
  const hashId = await resolveHashId(id, env);
  if (!hashId) return [];

  const versions = await listVersions(hashId, env);
  versions.sort((a, b) => b.score - a.score);
  return versions.slice(0, 10);
}

/** 關鍵字搜尋（掃描 KV prefix comp:，比對 canonical_id / display_name / description / aliases）
 *
 * 注意：這是 Phase 0 的純文字比對版本。
 * Phase 2 接入 Cloudflare Vectorize 後改為語意搜尋，API 介面不變。
 */
export async function searchComponents(
  query: string,
  env: Bindings,
): Promise<ComponentRecord[]> {
  const q = query.toLowerCase();

  // 列出所有 comp: 前綴的 key（只取最新一頁，最多 1000 個）
  const list = await env.SUBMISSIONS_KV.list({ prefix: 'comp:' });

  const seen = new Set<string>(); // 每個 hash_id 只取最優版本
  const candidates: ComponentRecord[] = [];

  for (const key of list.keys) {
    const raw = await env.SUBMISSIONS_KV.get(key.name);
    if (!raw) continue;
    let v: Record<string, unknown>;
    try { v = JSON.parse(raw); } catch { continue; }

    if (v.status === 'tombstone' || v.visibility !== 'public') continue;

    // 比對：canonical_id / display_name / description / aliases
    const searchable = [
      String(v.canonical_id ?? ''),
      String(v.display_name ?? ''),
      String(v.description ?? ''),
      ...(Array.isArray(v.aliases) ? v.aliases.map(String) : []),
      ...(Array.isArray(v.tags) ? v.tags.map(String) : []),
    ].join(' ').toLowerCase();

    if (!searchable.includes(q)) continue;

    const hashId = String(v.component_hash_id ?? '');
    if (seen.has(`${hashId}:${v.version}`)) continue;
    seen.add(`${hashId}:${v.version}`);
    candidates.push(toComponentRecord(v));
  }

  candidates.sort((a, b) => b.score - a.score);
  return candidates.slice(0, 10);
}

// ── 內部工具函數 ──────────────────────────────────────────────────────────────

// export：recordAnalytics 選「最優版本」要與 getComponent 同判準（單一評分真相）
export function computeScore(v: Record<string, unknown>): number {
  const successRate = parseFloat(String(v.success_rate ?? '1'));
  const avgDuration = parseFloat(String(v.avg_duration_ms ?? '10'));
  const callCount = parseInt(String(v.call_count ?? '0'), 10);
  const speedScore = Math.max(0, 1 - avgDuration / 1000);
  return successRate * speedScore * Math.log(callCount + 2);
}

export function toComponentRecord(v: Record<string, unknown>): ComponentRecord {
  return {
    component_hash_id: String(v.component_hash_id ?? ''),
    canonical_id: String(v.canonical_id ?? ''),
    display_name: String(v.display_name ?? ''),
    version: String(v.version ?? 'v1'),
    category: String(v.category ?? 'logic'),
    stability: String(v.stability ?? 'floating'),
    status: String(v.status ?? 'active'),
    description: String(v.description ?? ''),
    aliases: Array.isArray(v.aliases) ? v.aliases.map(String) : [],
    tags: Array.isArray(v.tags) ? v.tags.map(String) : [],
    success_rate: parseFloat(String(v.success_rate ?? '1')),
    avg_duration_ms: parseFloat(String(v.avg_duration_ms ?? '0')),
    call_count: parseInt(String(v.call_count ?? '0'), 10),
    wasm_r2_key: v.wasm_r2_key ? String(v.wasm_r2_key) : undefined,
    score: computeScore(v),
    input_schema: isPlainObject(v.input_schema) ? v.input_schema : undefined,
    output_schema: isPlainObject(v.output_schema) ? v.output_schema : undefined,
  };
}

function isPlainObject(x: unknown): x is Record<string, unknown> {
  return typeof x === 'object' && x !== null && !Array.isArray(x);
}
