#!/usr/bin/env node
/**
 * Bootstrap ~/.pilotdeck/pilotdeck.yaml when it doesn't exist yet, so the
 * gateway can boot and the Web UI can run the onboarding flow that fills in
 * real provider details. On every startup, also sync repo-provided skills
 * into ~/.pilotdeck/skills without overwriting existing targets.
 *
 * Behaviour:
 *   1. Every run: discover repo skills and copy missing slugs into
 *      $PILOT_HOME/skills, skipping existing targets.
 *   2. Every run: if pilotdeck.yaml exists but is missing known sections
 *      (e.g. adapters), append the default snippet so new features are
 *      discoverable without requiring users to recreate the config.
 *   3. If $PILOT_HOME/pilotdeck.yaml does not exist, write a minimal V2 yaml that:
 *        - has a valid agent.model that resolves to a catalog provider/model,
 *          so the engine's parseModelConfig won't crash on startup
 *        - uses a sentinel apiKey ("PLACEHOLDER_RUN_ONBOARDING_TO_REPLACE")
 *          that hasUsablePilotDeckConfig() recognises as "not ready" so the
 *          UI redirects to onboarding instead of pretending it's configured.
 *
 * Override the target via $PILOT_HOME (same env var the engine reads).
 * Skip the whole step via $PILOTDECK_SKIP_BOOTSTRAP=1.
 */
import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { basename, dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const SENTINEL_API_KEY = 'PLACEHOLDER_RUN_ONBOARDING_TO_REPLACE';
const __filename = fileURLToPath(import.meta.url);
const REPO_ROOT = resolve(dirname(__filename), '..');

const BOOTSTRAP_YAML = `# Auto-generated by scripts/bootstrap-pilotdeck-config.mjs.
# Open the Web UI to finish onboarding — choose a provider, paste an API key,
# and pick a model. The gateway boots with this placeholder config so the
# server can start, but apiKey="${SENTINEL_API_KEY}" tells the UI
# to redirect to the onboarding screen.
schemaVersion: 1
agent:
  model: _placeholder/_placeholder
model:
  providers:
    _placeholder:
      protocol: openai
      url: https://placeholder.invalid
      apiKey: ${SENTINEL_API_KEY}
      models:
        _placeholder:
          capabilities:
            maxOutputTokens: 16384
adapters:
  feishu:
    enabled: false
    appId: ""
    appSecret: ""
    # connectionMode: stream
    # domainName: feishu
router:
  enabled: true
  scenarios:
    default: _placeholder/_placeholder
  fallback:
    default:
      - _placeholder/_placeholder
  zeroUsageRetry:
    enabled: true
    maxAttempts: 2
  tokenSaver:
    enabled: true
    judge: _placeholder/_placeholder
    defaultTier: medium
    judgeTimeoutMs: 15000
    tiers:
      simple:
        model: _placeholder/_placeholder
        description: "Simple greetings, confirmations, single-step Q&A, trivial file writes, remembering rules"
      medium:
        model: _placeholder/_placeholder
        description: "Single tool call, short text generation, 1-2 file read/write, code generation"
      complex:
        model: _placeholder/_placeholder
        description: "Needs sub-agent orchestration: parallel workstreams, delegation to specialized agents"
      reasoning:
        model: _placeholder/_placeholder
        description: "Deep single-agent work: multi-file operations, data analysis, multi-step workflows, web research, structured reports from many sources"
    rules:
      - "complex is ONLY for tasks that need sub-agent orchestration or parallel delegation — do NOT use it for single-agent multi-step work"
      - "Multi-file operations, data analysis, and multi-step workflows without orchestration should be reasoning"
      - "Simple file creation (1-2 files) or single code generation is medium"
      - "Trivial greetings, confirmations, remembering rules, or reading one file and answering a short question is simple"
  autoOrchestrate:
    enabled: true
    triggerTiers:
      - complex
    slimSystemPrompt: true
    allowedTools:
      - agent
      - read_file
      - grep
      - glob
      - read_skill
    subagentMaxTokens: 48000
  stats:
    enabled: true
cron:
  enabled: true
  timezone: Asia/Shanghai
  maxConcurrentRuns: 2
`;

function resolvePilotHome() {
  if (process.env.PILOT_HOME) return process.env.PILOT_HOME;
  return join(homedir(), '.pilotdeck');
}

function discoverRepoSkillDirs(skillsRoot) {
  if (!existsSync(skillsRoot)) {
    return [];
  }

  const discovered = [];
  const pending = [skillsRoot];
  while (pending.length > 0) {
    const current = pending.pop();
    const entries = readdirSync(current, { withFileTypes: true });
    if (entries.some((entry) => entry.isFile() && /^skill\.md$/i.test(entry.name))) {
      discovered.push(current);
      continue;
    }
    for (const entry of entries) {
      if (entry.isDirectory()) {
        pending.push(join(current, entry.name));
      }
    }
  }

  discovered.sort((left, right) => left.localeCompare(right));
  return discovered;
}

function syncRepoSkillsToPilotHome(pilotHome) {
  const repoSkillsRoot = join(REPO_ROOT, 'skills');
  const skillDirs = discoverRepoSkillDirs(repoSkillsRoot);
  if (skillDirs.length === 0) {
    return { created: 0, skippedExisting: 0, skippedDuplicateSlug: 0 };
  }

  const targetRoot = join(pilotHome, 'skills');
  mkdirSync(targetRoot, { recursive: true });

  let created = 0;
  let skippedExisting = 0;
  let skippedDuplicateSlug = 0;
  const seenSlugs = new Map();

  for (const sourceDir of skillDirs) {
    const slug = basename(sourceDir);
    const previous = seenSlugs.get(slug);
    if (previous) {
      skippedDuplicateSlug += 1;
      console.warn(
        `[pilotdeck] Skipping repo skill '${slug}' at ${sourceDir}; slug already claimed by ${previous}.`,
      );
      continue;
    }
    seenSlugs.set(slug, sourceDir);

    const targetPath = join(targetRoot, slug);
    if (existsSync(targetPath)) {
      skippedExisting += 1;
      continue;
    }

    try {
      cpSync(sourceDir, targetPath, { recursive: true });
      created += 1;
    } catch (error) {
      console.warn(
        `[pilotdeck] Could not import repo skill '${slug}' into ${targetPath}: ${
          error instanceof Error ? error.message : String(error)
        }`,
      );
    }
  }

  return { created, skippedExisting, skippedDuplicateSlug };
}

const DEFAULT_ADAPTERS_SNIPPET = `
adapters:
  feishu:
    enabled: false
    appId: ""
    appSecret: ""
    # connectionMode: stream
    # domainName: feishu
`;

const DEFAULT_CRON_SNIPPET = `
cron:
  enabled: true
  timezone: Asia/Shanghai
  maxConcurrentRuns: 2
`;

const PATCH_SECTIONS = [
  { key: 'adapters', snippet: DEFAULT_ADAPTERS_SNIPPET },
  { key: 'cron', snippet: DEFAULT_CRON_SNIPPET },
];

function patchMissingSections(configPath) {
  let content;
  try {
    content = readFileSync(configPath, 'utf8');
  } catch {
    return;
  }

  let patched = false;
  for (const { key, snippet } of PATCH_SECTIONS) {
    const pattern = new RegExp(`^${key}\\s*:`, 'm');
    if (!pattern.test(content)) {
      try {
        appendFileSync(configPath, snippet, 'utf8');
        patched = true;
        console.log(`[pilotdeck] Appended missing "${key}" section to ${configPath}.`);
      } catch (error) {
        console.warn(
          `[pilotdeck] Could not append "${key}" to ${configPath}: ${
            error instanceof Error ? error.message : String(error)
          }`,
        );
      }
    }
  }

  return patched;
}

function main() {
  if (process.env.PILOTDECK_SKIP_BOOTSTRAP === '1') {
    return;
  }

  const pilotHome = resolvePilotHome();
  const configPath = join(pilotHome, 'pilotdeck.yaml');
  const skillSync = syncRepoSkillsToPilotHome(pilotHome);
  if (skillSync.created > 0 || skillSync.skippedExisting > 0 || skillSync.skippedDuplicateSlug > 0) {
    console.log(
      `[pilotdeck] Synced repo skills into ${join(pilotHome, 'skills')}: ` +
        `${skillSync.created} copied, ${skillSync.skippedExisting} skipped existing, ` +
        `${skillSync.skippedDuplicateSlug} skipped duplicate slug.`,
    );
  }

  if (existsSync(configPath)) {
    patchMissingSections(configPath);
    return;
  }

  try {
    mkdirSync(dirname(configPath), { recursive: true });
    writeFileSync(configPath, BOOTSTRAP_YAML, 'utf8');
    console.log(
      `[pilotdeck] No config at ${configPath}; wrote a placeholder so the gateway can boot.`,
    );
    console.log('[pilotdeck] Open the Web UI to finish onboarding (provider + API key).');
  } catch (error) {
    console.warn(
      `[pilotdeck] Could not bootstrap ${configPath}: ${error instanceof Error ? error.message : String(error)}`,
    );
    console.warn('[pilotdeck] You may need to create it manually before running npm run dev.');
  }
}

main();
