/**
 * Type definitions for the 1인 기업 (One-Person Company) mode.
 *
 * The mode turns the user into a virtual CEO that dispatches work to a roster
 * of specialist agents. Each turn produces a session directory containing the
 * CEO's brief, every specialist's output, and the final synthesis report. The
 * dispatcher runs agents *sequentially* — only one LLM is loaded at any
 * moment — so the user can run multiple distinct agents on a single
 * model-constrained machine without RAM thrash.
 */

/**
 * 직군(role category). Drives the pipeline editor's "직군 → 담당 에이전트"
 * cascading dropdown and the CEO planner's "no 개발 before 기획" guard rail.
 *
 * Why a fixed enum instead of free-text categories: a small LLM (gemma e2b
 * etc.) can't reliably *cluster* free-text specialties at plan time, but it
 * can absolutely reason about "planner first, developer second" when the
 * planner prompt enumerates these slugs. The UI also needs a stable set so
 * the dropdown is short and the icons are pickable.
 *
 *   ceo        — orchestrator (only the built-in CEO)
 *   planner    — 기획 (요구 정리·기획서·방향성 정의)
 *   researcher — 시장·트렌드·사실 확인 리서치
 *   designer   — UI/UX·비주얼·사운드·콘텐츠 디자인
 *   developer  — 코드 작성·API·자동화·배포 스크립트
 *   qa         — 테스트·버그 발굴·회귀 확인
 *   inspector  — 기획 의도 대비 산출물 감리 / 최종 승인
 *   support    — 비즈니스·운영·비서 (직접 산출물 X, 보조)
 */
export type AgentRoleCategory =
    | 'ceo'
    | 'planner'
    | 'researcher'
    | 'designer'
    | 'developer'
    | 'qa'
    | 'inspector'
    | 'support';

export const ROLE_CATEGORY_LABELS: Record<AgentRoleCategory, string> = {
    ceo: 'CEO',
    planner: '기획',
    researcher: '리서치',
    designer: '디자인',
    developer: '개발',
    qa: 'QA',
    inspector: '감리',
    support: '지원',
};

export const ROLE_CATEGORY_ORDER: AgentRoleCategory[] = [
    'ceo', 'planner', 'researcher', 'designer', 'developer', 'qa', 'inspector', 'support',
];

/** Static description of a company agent. Loaded from `agents.ts`. */
export interface CompanyAgentDef {
    /** Stable identifier used in JSON plans, file names, config keys. */
    id: string;
    /** Display name (may be a Korean nickname like "레오" or "코다리"). */
    name: string;
    /** Role title shown in the manage panel and used in system prompts. */
    role: string;
    /** Single emoji used in chat headers and chip badges. */
    emoji: string;
    /** Brand colour for the agent card UI. CSS hex. */
    color: string;
    /** Comma-list of areas this agent owns. Drives the CEO's planner. */
    specialty: string;
    /** One-line punchy tagline shown under the agent name. */
    tagline: string;
    /** Optional voice / personality directive injected into the system prompt. */
    persona?: string;
    /**
     * 직군 — drives pipeline editor cascading dropdowns and CEO planner
     * sequencing rules. Built-ins ship with a fixed category; users may
     * override via state.roleCategoryOverrides (custom agents pick at
     * create time and can re-edit later). Falls back to 'support' when
     * a legacy state entry is missing this field.
     */
    roleCategory: AgentRoleCategory;
    /**
     * When true, this agent can't be toggled off in the UI. CEO uses this so
     * it's always available as the orchestrator.
     */
    alwaysOn?: boolean;
}

/**
 * User edits to an agent's *prompt* surface. Each field is optional: empty
 * or missing means "use the static default from `agents.ts`". Storing this
 * in state (rather than rewriting `agents.ts`) means the user can re-tune
 * any agent's voice / specialty without touching code, and a `Reset` button
 * can wipe the override to recover the shipped default.
 */
export interface AgentPromptOverride {
    /** Replaces the persona block. Multi-line markdown allowed. */
    persona?: string;
    /** Replaces the specialty list shown to the CEO planner + specialist. */
    specialty?: string;
    /** Replaces the short tagline shown in the manage UI. */
    tagline?: string;
}

/**
 * User edits to an agent's *identity* surface (name / role title / emoji /
 * color). Unlike prompt overrides these don't affect the LLM's behaviour
 * directly — they change how the agent is *referred to* across the UI,
 * planner enumeration, and CEO report. Built-ins ship with fixed defaults;
 * the user can rename "레오" to "Lily" via this override without forking
 * the shipped roster. CEO planner's `_canonicalAgentId` already matches
 * against the effective (override-applied) name, so renaming an agent
 * still lets the LLM dispatch to it correctly.
 */
export interface AgentDisplayOverride {
    /** Replaces the display name (e.g. "레오" → "Lily"). */
    name?: string;
    /** Replaces the role title (e.g. "Head of YouTube" → "콘텐츠 디렉터"). */
    role?: string;
    /** Replaces the single emoji icon. */
    emoji?: string;
    /** Replaces the brand color (CSS hex). */
    color?: string;
}

/**
 * Persisted runtime state for the company mode. Stored in VS Code's
 * `globalState` plus a small JSON file under `.astra/company/_shared/`.
 */
export interface CompanyState {
    /** When false, the chip is shown but prompts route through normal chat. */
    enabled: boolean;
    /** User-facing name surfaced in CEO prompts and the chip badge. */
    companyName: string;
    /** Agents the user has toggled on. CEO is implicitly included. */
    activeAgentIds: string[];
    /**
     * Optional per-agent model override. Empty string / missing key means
     * "use the global default model". When the user assigns *different*
     * models to two agents, the LM Studio lifecycle manager unloads one and
     * loads the other between dispatches — RAM holds exactly one model at a
     * time, by design.
     */
    modelOverrides: Record<string, string>;
    /**
     * Optional per-agent prompt edits (persona / specialty / tagline).
     * Empty record = every agent uses its shipped default. The dispatcher
     * resolves the effective values via `resolveAgentPrompt` at build time
     * so changes apply on the very next turn — no restart required.
     */
    promptOverrides: Record<string, AgentPromptOverride>;
    /**
     * Optional per-agent Knowledge Mix override — same semantics as the
     * global `g1nation.knowledgeMix.secondBrainWeight` setting (0–100, where
     * higher means "lean on Second Brain evidence harder"). Missing key →
     * fall back to the global. Stored as integer.
     *
     * Why per-agent matters here: a Developer dispatch usually wants high
     * model knowledge (general coding patterns) and minimal brain noise,
     * while a Researcher / Writer benefits from heavy brain retrieval
     * because their job is to cite recorded knowledge.
     */
    knowledgeMixOverrides: Record<string, number>;
    /**
     * User-added agents. Merged with the built-in `COMPANY_AGENTS` roster at
     * read time — the built-ins are immutable code; everything in here is
     * user-defined and editable. Keys are agent ids (lowercase, kebab-case),
     * which must NOT collide with built-in ids.
     */
    customAgents?: Record<string, CompanyAgentDef>;
    /**
     * Per-agent 직군 override. Lets the user re-classify a built-in agent
     * without forking the code (e.g. promote Writer from `planner` to
     * `inspector` if they want that workflow). Missing key → use the
     * agent def's own `roleCategory`.
     */
    roleCategoryOverrides?: Record<string, AgentRoleCategory>;
    /**
     * Per-agent identity overrides (name / role / emoji / color). Same
     * semantics as `promptOverrides` but for the display surface — the
     * effective values are merged into the def at read time via
     * `resolveAgent`.
     */
    displayOverrides?: Record<string, AgentDisplayOverride>;
    /**
     * User-defined work pipelines. When `activePipelineId` is set to a key
     * here, the dispatcher runs the pipeline's stages in order instead of
     * letting the CEO planner emit ad-hoc tasks. Empty / unset → legacy
     * behaviour (CEO planner → linear dispatch).
     */
    pipelines?: Record<string, PipelineDef>;
    /**
     * Which pipeline drives this turn. `null` / undefined → legacy CEO
     * planner path. Must reference a key in `pipelines` or it is ignored.
     */
    activePipelineId?: string | null;
    /**
     * Built-in agent ids that the user has chosen to hide from the manage
     * panel. We can't truly delete a code-defined agent, but we can suppress
     * it from the UI / activation lists so the user perceives the same
     * "삭제" outcome. The `restoreHiddenAgent` path can put it back.
     *
     * Pipeline guards still apply at delete time — an agent referenced by
     * any pipeline stage cannot be moved into this list.
     */
    hiddenBuiltinIds?: string[];
}

/**
 * One step of a custom pipeline. Each stage runs a specific agent with a
 * specific instruction template (supports `{{userPrompt}}` and stage-output
 * variables like `{{stage.plan}}`). The stage can optionally loop back to
 * a previous stage when its output matches a regex — the canonical use
 * case is "QA finds a bug → go back to dev".
 */
export interface PipelineStage {
    /** Stable id within the pipeline. Used for `loopBackTo` references. */
    id: string;
    /** Human label shown in the chat phase header and the editor. */
    label: string;
    /**
     * 명시적으로 지정한 담당 에이전트. 비어 있거나 누락이면 dispatcher가
     * stage 진입 직전 CEO에게 "이 직군 중 누가 적합?" 한 줄 LLM 콜로
     * 결정 — 활성 에이전트가 1명뿐이면 콜 생략하고 그 사람 사용. 매번
     * 직군 후보 중에서 CEO가 고르는 게 사용자의 의도(*"CEO가 배분할
     * 에이전트를 판단"*)와 일치하므로 기본 권장값은 빈 문자열이다.
     */
    agentId?: string;
    /**
     * 직군 — *동적 담당자 선택의 핵심 필드*. agentId가 비어 있을 때
     * dispatcher는 이 직군 안에서만 후보를 추리고 CEO에게 고르게 한다.
     * agentId가 있어도 UI가 직군 dropdown을 원래 위치로 복원할 수 있게
     * 같이 저장 — 그 경우 dispatcher는 agentId가 우선이므로 무시.
     */
    roleCategory?: string;
    /**
     * Stage-level model override. Empty / missing → fall back to the
     * agent's own model override → global default. Use case: light planning
     * stages on a fast model (e.g. gemma e2b), heavy implementation stages
     * on a strong model (e.g. qwen 14b). LM Studio's lifecycle manager
     * swaps the model when the next stage's effective model differs.
     */
    modelOverride?: string;
    /**
     * Manual approval gate. When true, the dispatcher pauses after this
     * stage completes and waits for the user to click 승인 / 수정요청 /
     * 중단 in the chat. 수정요청은 같은 stage를 사용자 코멘트와 함께
     * 다시 실행. The pause is bounded by the existing turn-abort signal
     * — if the user kills the turn instead of clicking, the wait resolves
     * to "aborted" cleanly.
     */
    requiresApproval?: boolean;
    /**
     * 3-way 합의 검수 사이클을 켜는 스위치. 값 형식:
     *   - `'inspector'`              — `inspector` 직군의 활성 에이전트 자동 선임 (가장 흔한 케이스)
     *   - `'role:<roleCategory>'`    — 임의 직군 자동 선임 (예: `'role:qa'`)
     *   - `'agent:<agentId>'`        — 특정 에이전트 직접 지정
     *   - 빈값 / 미지정              — 검수 사이클 없음 (legacy 동작)
     *
     * 사이클은 매 라운드마다:
     *   1. 작업자 산출물 (이미 dispatch됨)
     *   2. 검수자가 "✅ 통과" 또는 "❌ 보완 필요: …" 로 시작하는 코멘트
     *   3. CEO가 메타-판단 "✅ 통과 / 🔁 보완 / 🛑 중단"
     * 검수자 ✅ + CEO ✅ → 통과 / 그 외 → 다음 라운드(또는 abort).
     */
    reviewWith?: string;
    /**
     * 검수 사이클 최대 라운드 수. 기본 3. 한도 도달하면 강제 통과(경고 표기).
     * 1 이상 10 이하 — 그 밖의 값은 normalize에서 clamp.
     */
    reviewMaxRounds?: number;
    /**
     * Instruction template. Tokens substituted before dispatch:
     *   - `{{userPrompt}}`        — what the user typed
     *   - `{{brief}}`             — CEO brief from the kickoff stage
     *   - `{{stage.<id>}}`        — full output of a previous stage
     * Empty → fall back to `{{userPrompt}}`.
     */
    instructionTemplate: string;
    /**
     * Regex (string form, case-insensitive) applied to this stage's output.
     * On match the dispatcher jumps back to `loopBackTo`. Iterations counted
     * — see `maxIterations`.
     */
    loopBackPattern?: string;
    /** Stage id to jump to on `loopBackPattern` match. Must precede this stage. */
    loopBackTo?: string;
    /** Safety cap on how many times this stage can loop back. Default 3. */
    maxIterations?: number;
}

export interface PipelineDef {
    id: string;
    name: string;
    stages: PipelineStage[];
}

/**
 * Intent Alignment — 사용자 자연어 요청을 *실행 가능한 작업 조건*으로 바꾼
 * 합의문. dispatcher는 turn을 시작하기 전 이 contract를 받고, CEO
 * planner / specialist prompt / 검수자 prompt 모두에 같은 ground truth로
 * 주입한다. 결과적으로 "에이전트가 사용자 머릿속을 추측하는" 단계를
 * 명시적인 데이터로 외부화한 것.
 *
 * 필드별 의미 (C-G-C-F-Q):
 *   - context           : 현재 상황·프로젝트 맥락. 사용자가 어디서 어떤 상태로
 *                         부터 출발하는지.
 *   - goal              : 사용자가 *달성하려는* 결과(behavioural, 1~2 문장).
 *   - criteria          : 좋은 결과의 판단 기준. 측정 가능한 형태가 이상적이지만
 *                         정성적 기준도 OK. 빈 배열일 수 있음.
 *   - format            : 원하는 산출물의 *형식* (예: "Markdown 기획서",
 *                         "Python 단일 스크립트", "JSON 데이터 + 짧은 요약").
 *   - answeredQuestions : alignment 라운드 동안 사용자가 답한 명확화 질문 + 응답
 *                         쌍. 기록용 — pipeline 단계에서도 같이 보여 줘서
 *                         "왜 이렇게 잡혔는지" 추적 가능.
 *   - openQuestions     : 분석기가 알고 싶었지만 사용자가 답 안 한(또는 안 받기로
 *                         결정한) 질문. 이게 비어 있지 않은 채로 dispatch되면
 *                         agent들에게 "이 부분은 모르니 보수적으로" 신호.
 *   - confidence        : alignment 단계의 자체 신뢰도. dispatcher가 모드에
 *                         따라 자동 진행 / 사용자 확인 / 추가 질문을 결정.
 */
export interface RequirementContract {
    userOriginalPrompt: string;
    context: string;
    goal: string;
    criteria: string[];
    format: string;
    answeredQuestions: Array<{ q: string; a: string }>;
    openQuestions: string[];
    confidence: 'low' | 'medium' | 'high';
}

/** Output of the CEO planner LLM call after JSON parsing. */
export interface CompanyTaskPlan {
    /** 2-3 sentence Korean summary of what the company is going to do. */
    brief: string;
    /** Ordered list of agent dispatches. Order is execution order. */
    tasks: Array<{
        /** Agent id (must exist in `AGENTS` and be active). */
        agent: string;
        /** Concrete, actionable instruction for the specialist. */
        task: string;
    }>;
}

/** One agent's contribution to a turn. */
export interface AgentTurnOutput {
    agentId: string;
    task: string;
    /** Raw LLM output, before action-tag execution. */
    response: string;
    /** Wall-clock milliseconds spent on this dispatch (LLM + tools). */
    durationMs: number;
    /** Populated when the dispatch failed; `response` then holds the error. */
    error?: string;
    /**
     * Lines returned by the action-tag executor — e.g. `"✅ Created: testtimer/timer.py"`.
     * Kept separate from `response` so downstream surfaces (Telegram mirror,
     * sidebar chip) can extract concrete file paths instead of regex-scraping
     * the prose. Empty / absent when the agent emitted no action tags.
     */
    actionReport?: string[];
}

/** The whole result of a company turn — persisted under sessions/<timestamp>/. */
export interface SessionResult {
    /** ISO timestamp used as the session directory name. */
    timestamp: string;
    /** Absolute filesystem path of the session directory. */
    sessionDir: string;
    /** What the user typed. */
    userPrompt: string;
    /** The CEO's plan that drove this turn. */
    plan: CompanyTaskPlan;
    /** Per-agent outputs, in execution order. */
    agentOutputs: AgentTurnOutput[];
    /** CEO's final synthesis. Empty when the synthesis call failed. */
    report: string;
    /** Walls-clock milliseconds from prompt arrival to report emission. */
    totalDurationMs: number;
}

/**
 * Persistent resume state for a partially-completed company turn.
 *
 * 동기: 사용자가 Stop 버튼을 누르거나, 네트워크/모델 오류로 turn이 중간에 끝나면
 * 지금까지 한 작업(planner 산출물 + 완료된 stage 출력)을 버리고 처음부터 다시
 * 돌리는 수밖에 없었음. 각 stage 직후·중단 시점에 `_resume.json`으로 직렬화하면
 * 사용자가 같은 세션을 이어서 진행할 수 있음.
 *
 * 1차 지원 범위: pipeline 모드(파이프라인 ID + stage 순서가 코드로 안정적).
 * Ad-hoc planner 모드도 같은 구조로 직렬화하지만, 재실행 시 CEO planner를
 * 재호출하지 않고 *원래 plan*을 그대로 사용 (재해석 일관성).
 */
export interface CompanyResumeState {
    /** Schema 버전 — 향후 마이그레이션 대비. 호환 안 되는 변경 시 ++. */
    version: 1;
    /** 세션 디렉터리 이름 (= timestamp). 절대경로 X — 머신 간 이식성 확보. */
    timestamp: string;
    /** 사용자가 처음 보낸 prompt. 재개 시에도 동일하게 사용. */
    userPrompt: string;
    /** Pipeline 모드면 그 id; ad-hoc planner면 null. */
    pipelineId: string | null;
    /** CEO planner 또는 파이프라인에서 파생된 작업 계획. 재개 시 그대로 재사용. */
    plan: CompanyTaskPlan;
    /**
     * 이미 끝낸 stage(파이프라인) 또는 task(ad-hoc) 출력. 재개 시 이 outputs는
     * 그대로 유지되고, 그 뒤 인덱스부터 dispatch 재개.
     */
    agentOutputs: AgentTurnOutput[];
    /** Pipeline 모드: 다음 실행할 stage의 0-based index. ad-hoc: 다음 task index. */
    nextIndex: number;
    /**
     * 파이프라인 모드의 보존 상태. loop-back / 수정요청 / 단계 출력 재참조가
     * 정상 재개되려면 같이 살아 있어야 함.
     */
    pipelineContext?: {
        /** stage.id → 최신 출력 (템플릿의 `{{stage.<id>}}` 치환용). */
        latestByStage: Record<string, AgentTurnOutput>;
        /** stage.id → loop-back 누적 횟수. maxIterations 가드용. */
        iterations: Record<string, number>;
        /** stage.id → 사용자가 수정요청 시 추가한 코멘트. */
        revisionNotes: Record<string, string>;
    };
    /**
     * 마지막 상태. 'in-progress'는 도중에 프로세스가 죽었을 때(크래시) 남는 값.
     * 'aborted'는 사용자가 명시적으로 Stop / 승인 게이트에서 abort.
     * 'completed' / 'failed'는 자연 종료 — 재개 목록에서 자동으로 빠진다.
     */
    status: 'in-progress' | 'aborted' | 'completed' | 'failed';
    /** abort 시 dispatcher가 emit한 reason ('aborted-mid-pipeline' 등). */
    abortReason?: string;
    /** 마지막 직렬화 시각 (ISO). 재개 목록 정렬 + UI 표시용. */
    lastUpdatedAt: string;
    /** 시작 시각 (ISO). UI에 "5분 전 시작" 형태로 노출. */
    startedAt: string;
}

/** Where on disk the company state lives, relative to the workspace root. */
export const COMPANY_DIR_REL = '.astra/company';
export const COMPANY_SHARED_REL = `${COMPANY_DIR_REL}/_shared`;
export const COMPANY_SESSIONS_REL = `${COMPANY_DIR_REL}/sessions`;
export const COMPANY_AGENTS_REL = `${COMPANY_DIR_REL}/_agents`;

/** State-key namespaces used in VS Code's globalState. */
export const COMPANY_STATE_KEY = 'g1nation.company.state';
