/**
 * System-prompt construction for company-mode agents.
 *
 * Each specialist needs a prompt that includes:
 *   - Their identity (name, role, specialty) + optional persona.
 *   - The action-tag contract (`<create_file>`, `<run_command>`, etc.) so
 *     ConnectAI's existing `_executeActions()` can handle tool calls
 *     transparently after the LLM responds.
 *   - The *peer context* — earlier agents' outputs in the same turn, so the
 *     second/third agent can build on what came before.
 *   - The agent's long-term memory (`memory.md`) when available.
 *   - Company-wide decisions, if recorded.
 *
 * Build-once-per-dispatch: the dispatcher calls `buildSpecialistPrompt()` for
 * every task. Each call is pure (no I/O of its own — the caller fetches
 * memory/decisions and passes them in), which keeps it trivial to test.
 */
import { COMPANY_AGENTS } from './agents';
import { listAllAgents, listActiveAgentsByCategory, resolveAgent, resolveAgentPrompt } from './companyConfig';
import { CompanyState, ROLE_CATEGORY_LABELS } from './types';

export interface SpecialistPromptInputs {
    /** Active agent id. Must exist in `COMPANY_AGENTS`. */
    agentId: string;
    /** Current persisted company state (used for company name + context). */
    state: CompanyState;
    /** Long-term agent memory text (may be empty). Pre-read by caller. */
    agentMemory?: string;
    /** Tail of `_shared/decisions.md` (may be empty). */
    sharedDecisions?: string;
    /**
     * `_shared/calendar_cache.md` 내용 (Google Calendar iCal 읽기 전용 feature).
     * 비어있으면 prompt 에 섹션 추가 안 함. 모든 agent 가 동일하게 받음 — 일정·운영을
     * 다루는 secretary 직군이 가장 활용 가능성 높지만, 기획·개발도 "이번 주 빈 슬롯"
     * 같은 추론에 쓸 수 있음.
     */
    calendarContext?: string;
    /**
     * Task tracker (`_shared/tasks.md`) active 항목 요약. 모든 agent 가 진척 상황 + 막힌
     * task 를 인지하고 일을 분배·재조정. 비어있으면 섹션 생략.
     */
    tasksContext?: string;
    /**
     * Peer outputs from earlier agents in *this* dispatch, in execution order.
     * Truncated by the dispatcher before passing — this builder doesn't trim
     * again so we don't double-pay tokens for one transformation.
     */
    peerOutputs?: Array<{ agentId: string; agentName: string; emoji: string; content: string }>;
    /**
     * Pre-rendered Second Brain context block (from `buildContextBlock` in
     * scopedBrainRetriever). Empty when retrieval found nothing or
     * Knowledge Mix weight is 0. Inserted as evidence the specialist can
     * cite — `[제2뇌 컨텍스트 ...]` header already included by the builder.
     */
    brainContext?: string;
    /**
     * Pre-rendered Knowledge Mix policy block (from `buildKnowledgeMixPolicy`).
     * Empty when the weight is the neutral 50 (no policy needed).
     * Tells the specialist how heavily to rely on the brain context.
     */
    knowledgeMixPolicy?: string;
    /**
     * Intent Alignment 단계에서 도출된 사용자 합의 contract 블록 (이미 마크다운
     * 으로 직렬화된 상태). 있으면 시스템 프롬프트의 identity 다음·output 규칙
     * 직전에 prepend 되어 *모든 후속 지시보다 우선하는* ground truth로 동작.
     */
    contractBlock?: string;
}

/**
 * Build the full system prompt for one specialist. Returns plain markdown.
 *
 * The structure favours *short headed sections* over one giant blob because
 * smaller local models (≤7B) respect markdown-headed blocks better than
 * dense paragraphs. Order matters: identity first, then rules, then context.
 */
export function buildSpecialistPrompt(inputs: SpecialistPromptInputs): string {
    const agent = resolveAgent(inputs.state, inputs.agentId);
    if (!agent) {
        // Defensive fallback — should never happen because the dispatcher
        // filters tasks against the active agent set before calling us.
        return `You are an agent named "${inputs.agentId}". Respond in Korean.`;
    }
    const company = inputs.state.companyName || '1인 기업';
    // Honour any per-agent prompt overrides the user saved in the manage
    // panel. `resolveAgentPrompt` falls back to the static defaults from
    // `agents.ts` for fields the user hasn't edited.
    const resolved = resolveAgentPrompt(inputs.state, inputs.agentId);
    const parts: string[] = [];

    // ── Identity ──
    parts.push(`# ${agent.emoji} ${agent.name} — ${agent.role}`);
    parts.push(`당신은 ${company}의 ${agent.role}입니다.`);
    parts.push(`전문 분야: ${resolved.specialty}`);
    if (resolved.persona) {
        parts.push('');
        parts.push('## 페르소나');
        parts.push(resolved.persona);
    }

    // ── Requirement Contract (Intent Alignment) ──
    // alignment 단계를 거쳤다면 사용자와 합의된 contract가 모든 룰 위에 온다.
    // 어떤 페르소나·검색 컨텍스트보다도 우선이라는 신호로 출력 규칙 *앞*에 prepend.
    if (inputs.contractBlock && inputs.contractBlock.trim()) {
        parts.push('');
        parts.push(inputs.contractBlock.trim());
    }

    // ── Output contract ──
    parts.push('');
    parts.push('## 출력 규칙');
    parts.push('- 한국어 마크다운으로 답변. 사장님(사용자)에게 보고하는 톤.');
    parts.push('- 작업이 끝나면 마지막에 두 줄로 자기 평가를 붙이세요:');
    parts.push('  - `📊 평가:` 한 줄로 산출물의 가치(데이터 기반·완성도·아이디어 신선도).');
    parts.push('  - `📝 다음:` 사장님 입장에서 다음에 할 만한 한 가지 액션 한 줄.');
    parts.push('- 추측·일반론·placeholder 금지. 가진 정보만 인용.');

    // ── Tool contract ──
    // Hard rule about action tags. Earlier wording ("태그 없이 평문으로만
    // 답해도 됩니다") let small models (gemma 4 e2b etc.) emit ```python
    // code blocks and then *claim* to have created files — the user got
    // ✅ in chat but nothing on disk. This block is now phrased so the
    // model cannot rationalise its way out of the tag contract.
    parts.push('');
    parts.push('## ⚠️ 실제 파일·명령 실행 (이 섹션 매우 중요)');
    parts.push('당신은 사용자의 **실제** 파일 시스템과 터미널에 직접 연결되어 있습니다.');
    parts.push('**텍스트로 "만들었다 / 작성했다 / 생성했다 / 저장했다" 라고 말해도 사용자 디스크엔 아무 일도 안 일어납니다.**');
    parts.push('파일을 만들거나 명령을 실행하려면 **반드시** 아래 액션 태그로 감싸세요. 시스템이 자동으로 디스크에 적용합니다:');
    parts.push('');
    parts.push('  • `<create_file path="...">내용</create_file>` — 새 파일 생성·덮어쓰기');
    parts.push('  • `<edit_file path="..."><find>옛 내용</find><replace>새 내용</replace></edit_file>` — 부분 편집');
    parts.push('  • `<read_file path="..."/>` — 32KB까지 읽기 (편집 전엔 반드시 먼저 read)');
    parts.push('  • `<delete_file path="..."/>` — 파일·디렉토리 삭제');
    parts.push('  • `<list_files path="..."/>` — 디렉토리 목록 보기');
    parts.push('  • `<run_command>명령</run_command>` — 셸 실행 (디렉토리 생성 등)');
    parts.push('  • `<create_calendar_event title="..." start="2026-05-21T14:00" duration="60" location="...">설명</create_calendar_event>` — Google Calendar 일정 자동 생성 (OAuth 연결 필요)');
    parts.push('  • `<read_sheet spreadsheet_id="..." range="Sheet1!A1:D20"/>` — Google Sheets 셀 범위 읽기');
    parts.push('  • `<write_sheet spreadsheet_id="..." range="Sheet1!A1">TSV 본문</write_sheet>` — 셀 덮어쓰기 (탭 구분, 줄바꿈 = 행)');
    parts.push('  • `<append_sheet spreadsheet_id="..." range="Sheet1!A:C">TSV 본문</append_sheet>` — 마지막 데이터 행 아래에 append');
    parts.push('  • `<add_task title="..." owner="@me" due="2026-05-24T18:00" notes="..."/>` — 작업 추적기에 task 추가');
    parts.push('  • `<update_task id="t_001" status="in_progress|blocked|done" notes="..."/>` — 진척·blocker 갱신');
    parts.push('  • `<complete_task id="t_001"/>` — task 완료 처리 (done 섹션으로 이동)');
    parts.push('  • `<fetch_url url="https://..."/>` — 웹 페이지 본문 가져오기 (회당 최대 2개). "사이트 방문 불가"라고 답하지 말고 이 태그를 사용할 것. 결과는 [URL CONTENT] 블록으로 주입됨.');
    parts.push('');
    parts.push('📋 **Task 사용 시점**:');
    parts.push('- 회의록·요청 처리 중 *명확한 할일* 마다 add_task 1개씩 emit. 추측·확장 금지.');
    parts.push('- 사용자가 진척 보고하면 즉시 update_task / complete_task. "추적해뒀어요" 라고 말만 하지 말 것.');
    parts.push('- due 가 분 단위로 명확하면 add_task + create_calendar_event 함께 emit — 추적기에도, 캘린더에도.');
    parts.push('');
    parts.push('📅 **Calendar 사용 시점**:');
    parts.push('- 사용자가 회의록 / 안건 / "X 일까지 끝내야 해" 같이 *명확한 시각이 있는* 약속·작업을 공유했을 때.');
    parts.push('- 시간이 모호하면 ("다음주에", "조만간") 태그 emit 하지 말고 *물어봐서 확정* 한 뒤 emit.');
    parts.push('- 회의록에 여러 일정/할일이 섞여 있으면 *각각 1개 태그씩* emit. 한 태그에 여러 일정 욱여넣지 말 것.');
    parts.push('');
    parts.push('📊 **Sheets 사용 시점**:');
    parts.push('- spreadsheet_id 는 사용자가 *직접 알려준 것만* 사용. 추측 / 생성 금지.');
    parts.push('- 쓰기 전엔 한 줄로 "어디에 무엇을 쓰겠다" 미리 보고. 사용자가 stop 안 걸면 즉시 태그 emit.');
    parts.push('- 본문은 TSV (탭 구분, 줄바꿈 = 행). 한 줄에 칼럼이 여러개면 탭으로 구분.');
    parts.push('');
    parts.push('🛑 **경로 규칙 (위반 시 권한 거부됨)**:');
    parts.push('- 경로는 **워크스페이스 루트 상대 경로**로 쓰세요. 예: `timertest/timer.py`, `src/utils.py`');
    parts.push('- 절대 경로 가능하지만 **반드시 워크스페이스 내부**여야 함. `/antigravity/...` `/tmp/...` 같은 시스템 루트 경로는 거부됨.');
    parts.push('- 디렉토리는 `<create_file>`이 자동으로 만들어줍니다 (mkdir -p). 별도 명령 불필요.');
    parts.push('');
    parts.push('❌ **하지 말아야 할 패턴**:');
    parts.push('  - ```python\\nprint("...")\\n``` 코드 블록만 답하고 "생성 완료"라고 말하기 → 디스크엔 만들어지지 않음');
    parts.push('  - `<create_file path="/antigravity/foo.py">` 같은 시스템 루트 경로 → 거부됨');
    parts.push('  - 사용자가 "X 만들어줘"라고 했는데 코드만 보여주고 끝내기 → 사용자는 결과물을 받지 못함');
    parts.push('');
    parts.push('✅ **올바른 패턴**:');
    parts.push('  사용자: "타이머 파이썬 스크립트 만들어줘"');
    parts.push('  당신: 짧은 설명 한두 줄 + `<create_file path="timer.py">import time\\n...</create_file>` + 자가평가');
    parts.push('');
    parts.push('기획·분석·아이디어처럼 *결과물이 파일 아닌 경우*에는 액션 태그 없이 마크다운으로만 답해도 됩니다.');

    // ── Peer context (this turn) ──
    const peers = inputs.peerOutputs ?? [];
    if (peers.length > 0) {
        parts.push('');
        parts.push('## 같은 세션의 동료 산출물');
        parts.push('아래는 당신보다 먼저 작업한 동료들의 결과입니다. 인용·참조해서 일관된 흐름을 만드세요.');
        for (const p of peers) {
            parts.push('');
            parts.push(`### ${p.emoji} ${p.agentName}`);
            parts.push(p.content);
        }
    }

    // ── Second Brain context (RAG) ──
    // Pre-rendered by the dispatcher via `retrieveScoped + buildContextBlock`.
    // The block already carries its own `[제2뇌 컨텍스트 ...]` header so we
    // just sandwich it. Empty when retrieval yielded nothing.
    const brain = (inputs.brainContext ?? '').trim();
    if (brain) {
        parts.push('');
        parts.push(brain);
    }

    // ── Knowledge Mix policy ──
    // Tells the agent how to *use* the Second Brain context above (or
    // ignore it when the weight is low). Skipped at the neutral weight 50
    // so we don't add noise when there's nothing distinctive to say.
    const policy = (inputs.knowledgeMixPolicy ?? '').trim();
    if (policy) {
        parts.push('');
        parts.push(policy);
    }

    // ── Long-term memory ──
    const memory = (inputs.agentMemory ?? '').trim();
    if (memory) {
        parts.push('');
        parts.push('## 당신의 장기 기억 (memory.md)');
        parts.push('과거 작업에서 누적된 학습입니다. 지금 task와 충돌하면 *현재 task가 우선*입니다.');
        parts.push(memory);
    }

    // ── Company-wide decisions ──
    const decisions = (inputs.sharedDecisions ?? '').trim();
    if (decisions) {
        parts.push('');
        parts.push('## 회사 공통 결정 사항 (decisions.md)');
        parts.push(decisions);
    }

    // ── Google Calendar (iCal 읽기 전용) ──
    // 모든 agent 가 받는 외부 컨텍스트 — "이번 주 화/목 14:00-16:00 비어있다",
    // "내일 13:00 미팅" 같은 정보. 캐시 파일은 g1nation.calendar.refresh 명령 또는
    // 셋업 시점에 갱신됨 — turn 마다 fetch 하면 외부 의존성 + 지연 발생하므로 보수적.
    const calendarCtx = (inputs.calendarContext ?? '').trim();
    if (calendarCtx) {
        parts.push('');
        parts.push('## 사용자 일정 컨텍스트 (Google Calendar)');
        parts.push(calendarCtx);
    }

    // ── Task tracker — active 작업 한 눈에 ──
    const tasksCtx = (inputs.tasksContext ?? '').trim();
    if (tasksCtx) {
        parts.push('');
        parts.push('## 진행 중인 작업 (tasks.md · active)');
        parts.push('아래는 회사 작업 추적기에 active 로 들어있는 task 들입니다. 진척 / 막힌 항목 / 다가오는 마감을 인지하고 답변에 반영하세요. 사용자가 진척을 알려주면 `<update_task>` 또는 `<complete_task>` 로 즉시 갱신.');
        parts.push(tasksCtx);
    }

    // ── Self-Reflector Phase A 룰 (가장 마지막에 prepend) ──
    // 답변 끝에 [Self-Reflector Check] 블록을 자동으로 붙이게 만든다. developer
    // 직군이면 코드 가드 블록도 함께 — undefined variable / 잘못된 경로 같은
    // 코드 답변 최빈출 실수에 직격타.
    try {
        const { getConfig } = require('../../config') as typeof import('../../config');
        const { appendSelfReflectorRule } = require('../selfReflector/selfReflectorPrompt') as typeof import('../selfReflector/selfReflectorPrompt');
        const cfg = getConfig();
        if (cfg.selfReflectorEnabled) {
            const base = parts.join('\n');
            const isCoder = agent.roleCategory === 'developer';
            return appendSelfReflectorRule(base, { enabled: true, includeCodeGuard: isCoder });
        }
    } catch { /* fall through */ }

    return parts.join('\n');
}

/**
 * Build the planner system prompt. The base template is in `promptAssets.ts`;
 * this helper layers on the currently active agent list so the planner can't
 * dispatch to a disabled specialist.
 */
export function buildPlannerSystemPrompt(
    baseTemplate: string,
    state: CompanyState,
): string {
    const active = new Set<string>(state.activeAgentIds);
    active.add('ceo');
    // Built-ins + custom agents — both can be deactivated by the user.
    const allIds = listAllAgents(state).map((a) => a.id);
    const inactive = allIds.filter((id) => !active.has(id));
    const tail: string[] = [];

    // ── 직군별 활성 에이전트 카탈로그 + 직군 가드 ───────────────────────────
    // 작은 LLM (gemma e2b/4b 등) 은 사용자가 "코딩해줘" 한 마디만 해도
    // developer로 직행하는 anchor bias가 강하다. 직군 가드 룰을 명시해서
    // "개발 task가 있으려면 그 전에 planner task가 반드시 있어야 한다"
    // 를 박아둔다. 100% 작동하진 않지만 도움이 됨. 더 확실히 가드하려면
    // 사용자가 pipeline을 켜면 된다 — 그쪽은 deterministic.
    const buckets = listActiveAgentsByCategory(state);
    const usedCats = (Object.keys(buckets) as (keyof typeof buckets)[])
        .filter((cat) => cat !== 'ceo' && buckets[cat].length > 0);
    if (usedCats.length > 0) {
        tail.push('');
        tail.push('## 직군(role) 기반 작업 분배 규칙');
        tail.push('이번 라운드에 활성화된 직군별 에이전트:');
        for (const cat of usedCats) {
            const label = ROLE_CATEGORY_LABELS[cat] || cat;
            const names = buckets[cat].map((a) => `${a.emoji}${a.name}(${a.id})`).join(', ');
            tail.push(`- **${label}** [${cat}]: ${names}`);
        }
        tail.push('');
        tail.push('🛑 **직군 순서 가드 (반드시 준수)**:');
        tail.push('1. `developer` 직군에게 코드 작성 task를 줄 때는 그 *전에* `planner` 직군의 task가 tasks 배열 안에 반드시 하나 이상 있어야 합니다. 사용자가 "지금 당장 만들어"라고 명시했어도 마찬가지 — 한 줄짜리 기획 정리도 planner task로 먼저 넣으세요.');
        tail.push('2. `developer`·`designer`에게 task를 주는데 사용자 요청이 모호하다면, 그 전에 `researcher` 또는 `planner` task로 *맥락을 정리*하는 step을 먼저 넣으세요.');
        tail.push('3. `qa`·`inspector` 직군은 *반드시 검토 대상이 되는 산출물 task가 같은 라운드에 존재할 때만* 호출하세요. 검토할 게 없는데 QA만 호출하면 빈 검토가 됩니다.');
        tail.push('4. 사용자가 "기획 없이 바로 코드"라고 명시적으로 말한 경우에만 1번 규칙을 우회 가능 — 그 외엔 위반 금지.');
    }

    if (inactive.length > 0) {
        tail.push('');
        tail.push('현재 비활성화된 에이전트 (절대 dispatch 금지):');
        for (const id of inactive) {
            const def = resolveAgent(state, id) ?? COMPANY_AGENTS[id];
            tail.push(`- ${id} (${def?.name ?? id})`);
        }
    }
    return baseTemplate + tail.join('\n');
}
