/**
 * Stage 1 — Haiku triage.
 *
 * Takes the pre-processed ticket and asks Haiku 4.5 to extract structured
 * metadata and assess complexity. Output is the `TriageResponse` schema.
 *
 * Spec: docs/wulf-pulse-ticket-analyzer-prompt.md → "Stage 1 — Triage (Haiku)"
 */

import { TriageResponse, type PreprocessedTicket, type TaggedEvent } from '@/lib/types/analyzer';
import { callLLMStage, type LLMCallResult } from '@/lib/services/llm/call';
import { HAIKU, type ModelId } from '@/lib/services/llm/models';
import type Anthropic from '@anthropic-ai/sdk';

const STAGE1_MAX_TOKENS = 4_000;

/**
 * Approx 50KB cap on the user payload, per spec. We measure in characters
 * (rough proxy for tokens — typical English is ~4 chars per token, so 50KB ≈
 * 12.5K tokens of input).
 */
const PAYLOAD_CHAR_CAP = 50_000;

const SYSTEM_PROMPT = `You are a ticket triage assistant for Wulf Consulting, an MSP. You will receive an Autotask ticket with notes and time entries that have already been pre-filtered to remove workflow noise and tagged by visibility (customer-facing vs internal).

Extract structured metadata and assess complexity. Pay special attention to internal-only notes — these often contain the real story (e.g. customer indicating they're handling something themselves, status drift, billed time the customer didn't ultimately need).

Tag actor_type by the email domain we already classified for you, not by what the message says. A note from \`@wulfconsulting.com\` is internal even when it sounds customer-facing.

Respond ONLY with JSON. No prose, no code fences.

Schema:
{
  "ticket_type": "incident" | "service_request" | "problem" | "change" | "other",
  "category": string,
  "entities": {
    "client_name": string | null,
    "site_name": string | null,
    "devices": string[],
    "users": string[],
    "applications": string[],
    "vendors": string[]
  },
  "is_resolved": boolean,
  "status_matches_reality": boolean,
  "complexity_tier": "low" | "medium" | "high",
  "complexity_reasons": string[],
  "itglue_lookup_needed": boolean,
  "itglue_search_hints": string[]
}

Complexity rubric:
- low: single straightforward issue, ≤3 retained events, clear path
- medium: multiple events, some back-and-forth, moderate ambiguity
- high: any of — bounced between techs, conflicting notes, unresolved >5 days, customer-vs-internal narrative mismatch, multiple vendors involved, or status appears to disagree with the actual state of the work

Set itglue_lookup_needed = true when answering this ticket reliably likely depends on configuration, runbook, or password material we'd document in IT Glue. Do not set it true for purely conversational or scheduling tickets.`;

/**
 * Build the user payload string. Caps total size at ~50KB; if larger, drops
 * oldest internal-only events first (preserving every customer-facing event)
 * and adds a marker. Customer-facing events are never dropped.
 */
export function buildTriageUserPayload(pre: PreprocessedTicket): {
  payload: string;
  events_dropped: number;
} {
  const headerJson = JSON.stringify(pre.header, null, 2);
  const countsJson = JSON.stringify(pre.counts, null, 2);
  const events = pre.events.slice();

  let droppedNotice = '';
  let eventsJson = JSON.stringify(events, null, 2);

  // Crude payload sizing — we serialize, measure, and if oversized drop oldest
  // internal-only events one-by-one until we fit. This is rare in practice.
  let droppedCount = 0;
  while (
    headerJson.length + countsJson.length + eventsJson.length > PAYLOAD_CHAR_CAP
  ) {
    const oldestInternalIdx = events.findIndex((e) => e.visibility === 'internal_only');
    if (oldestInternalIdx === -1) break; // nothing safe left to drop
    events.splice(oldestInternalIdx, 1);
    droppedCount += 1;
    eventsJson = JSON.stringify(events, null, 2);
  }
  if (droppedCount > 0) {
    droppedNotice = `\n\nNOTE: ${droppedCount} oldest internal-only event(s) were dropped from this payload to fit the size cap. All customer-facing events are preserved.`;
  }

  const payload = [
    `=== TICKET HEADER ===`,
    headerJson,
    ``,
    `=== EVENT COUNTS ===`,
    countsJson,
    ``,
    `=== TAGGED EVENTS (chronological) ===`,
    eventsJson,
    droppedNotice,
  ].join('\n');

  return { payload, events_dropped: droppedCount };
}

export interface TriageStageResult extends LLMCallResult<TriageResponse> {
  events_dropped: number;
}

export async function runTriageStage(
  pre: PreprocessedTicket,
  injectedClient?: Anthropic,
  modelOverride?: ModelId
): Promise<TriageStageResult> {
  const { payload, events_dropped } = buildTriageUserPayload(pre);

  const result = await callLLMStage({
    model: modelOverride ?? HAIKU,
    system: SYSTEM_PROMPT,
    user: payload,
    schema: TriageResponse,
    maxTokens: STAGE1_MAX_TOKENS,
    client: injectedClient,
  });

  return { ...result, events_dropped };
}

// For tests + observability.
export const _STAGE1_INTERNALS = {
  SYSTEM_PROMPT,
  PAYLOAD_CHAR_CAP,
  STAGE1_MAX_TOKENS,
};

// Suppress unused-import warning if TaggedEvent isn't referenced by
// callers in this file but is part of the public type surface.
export type { TaggedEvent };
