import { recallByTag, type SynapRecallMemory } from "./synap";
import { loadProfile, loadCohortPriors } from "./cohorts";
import type {
  CommunicationBrief,
  TopicEntry,
  PersonProfile,
} from "./types";

// ---------------------------------------------------------------------------
// Signal content parsers — match exact formats from observer.ts
// ---------------------------------------------------------------------------

interface ParsedTopic {
  topic: string;
  domain: string;
  specificity: "surface" | "intermediate" | "expert";
  deepened: boolean;
  continued: boolean;
}

/** Parse: "topic: distributed systems (engineering, expert) [deepened] [continued]" */
function parseTopicContent(content: string): ParsedTopic | null {
  const match = content.match(
    /^topic:\s*(.+?)\s*\((\w+),\s*(surface|intermediate|expert)\)/
  );
  if (!match) return null;
  return {
    topic: match[1].trim(),
    domain: match[2].trim(),
    specificity: match[3] as ParsedTopic["specificity"],
    deepened: content.includes("[deepened]"),
    continued: content.includes("[continued]"),
  };
}

interface ParsedStyle {
  formality: number;
  lowercase: boolean;
  jargon: boolean;
  structure: string;
  emoji: boolean;
}

/** Parse: "formality: 0.25, lowercase: true, jargon: false, structure: stream_of_thought, emoji: false" */
function parseStyleContent(content: string): ParsedStyle | null {
  const formality = content.match(/formality:\s*([\d.]+)/);
  if (!formality) return null;
  return {
    formality: parseFloat(formality[1]),
    lowercase: content.includes("lowercase: true"),
    jargon: content.includes("jargon: true"),
    structure:
      content.match(/structure:\s*(\w+)/)?.[1] ?? "stream_of_thought",
    emoji: content.includes("emoji: true"),
  };
}

interface ParsedDynamics {
  leading: "person" | "system";
  builtOnPrevious: boolean;
  redirected: string | null;
  correctedSystem: boolean;
}

/** Parse: "leading: person, built_on_previous: true, redirected: X, corrected system" */
function parseDynamicsContent(content: string): ParsedDynamics | null {
  const leading = content.match(/leading:\s*(person|system)/);
  if (!leading) return null;
  const redirectMatch = content.match(/redirected:\s*(.+?)(?:,|$)/);
  return {
    leading: leading[1] as "person" | "system",
    builtOnPrevious: content.includes("built_on_previous: true"),
    redirected: redirectMatch ? redirectMatch[1].trim() : null,
    correctedSystem: content.includes("corrected system"),
  };
}

interface ParsedEngagement {
  sentiment: number;
  direction: string;
  substantive: boolean;
  words: number;
}

/** Parse: "sentiment: 0.78 (positive), substantive: true, words: 42" */
function parseEngagementContent(content: string): ParsedEngagement | null {
  const sentiment = content.match(/sentiment:\s*([\d.]+)\s*\((\w+)\)/);
  if (!sentiment) return null;
  const words = content.match(/words:\s*(\d+)/);
  return {
    sentiment: parseFloat(sentiment[1]),
    direction: sentiment[2],
    substantive: content.includes("substantive: true"),
    words: words ? parseInt(words[1], 10) : 0,
  };
}

// ---------------------------------------------------------------------------
// Section builders
// ---------------------------------------------------------------------------

function buildTopicsSection(
  memories: SynapRecallMemory[]
): CommunicationBrief["topics"] {
  const topicMap = new Map<
    string,
    {
      domain: string;
      specificity: "surface" | "intermediate" | "expert";
      deepened: boolean;
      count: number;
      lastSeen: number;
    }
  >();

  for (let i = 0; i < memories.length; i++) {
    const parsed = parseTopicContent(memories[i].content);
    if (!parsed) continue;

    const key = parsed.topic.toLowerCase();
    const existing = topicMap.get(key);
    if (existing) {
      existing.count++;
      existing.lastSeen = i;
      if (parsed.deepened) existing.deepened = true;
      // Keep highest specificity
      const specOrder = { surface: 0, intermediate: 1, expert: 2 };
      if (specOrder[parsed.specificity] > specOrder[existing.specificity]) {
        existing.specificity = parsed.specificity;
      }
    } else {
      topicMap.set(key, {
        domain: parsed.domain,
        specificity: parsed.specificity,
        deepened: parsed.deepened,
        count: 1,
        lastSeen: i,
      });
    }
  }

  // Sort by recency * frequency (higher = hotter)
  const entries = [...topicMap.entries()]
    .map(([topic, data]) => ({
      topic,
      ...data,
      score: data.count * (1 + data.lastSeen / memories.length),
    }))
    .sort((a, b) => b.score - a.score);

  const hot: TopicEntry[] = entries.slice(0, 5).map((e) => ({
    topic: e.topic,
    domain: e.domain,
    frequency: e.count,
    specificity: e.specificity,
    deepened: e.deepened,
  }));

  // Cold: known topics not in the hot list, sorted by count descending
  const cold: TopicEntry[] = entries.slice(5, 8).map((e) => ({
    topic: e.topic,
    domain: e.domain,
    frequency: e.count,
    specificity: e.specificity,
    deepened: e.deepened,
  }));

  // Domains by frequency
  const domainCount = new Map<string, number>();
  for (const e of entries) {
    domainCount.set(e.domain, (domainCount.get(e.domain) ?? 0) + e.count);
  }
  const domains = [...domainCount.entries()]
    .sort((a, b) => b[1] - a[1])
    .slice(0, 5)
    .map(([d]) => d);

  return { hot, cold, domains };
}

function buildStyleSection(
  profile: PersonProfile | null,
  styleMemories: SynapRecallMemory[]
): CommunicationBrief["style"] {
  // Default from profile
  let formality = profile?.avgFormality ?? 0.5;
  let jargon = (profile?.jargonRate ?? 0) > 0.5;
  let emoji = false;
  let structure = "stream_of_thought";
  let avgWords = profile?.avgWordCount ?? 20;

  // Aggregate from recent signals — average numerics, majority-vote booleans/strings
  let styleCount = 0;
  let formalitySum = 0;
  let jargonTrue = 0;
  let emojiTrue = 0;
  const structureCounts = new Map<string, number>();

  for (const mem of styleMemories) {
    const parsed = parseStyleContent(mem.content);
    if (!parsed) continue;
    styleCount++;
    formalitySum += parsed.formality;
    if (parsed.jargon) jargonTrue++;
    if (parsed.emoji) emojiTrue++;
    structureCounts.set(parsed.structure, (structureCounts.get(parsed.structure) ?? 0) + 1);
  }

  if (styleCount > 0) {
    formality = formalitySum / styleCount;
    jargon = jargonTrue / styleCount > 0.5;
    emoji = emojiTrue / styleCount > 0.5;
    // Most frequent structure wins
    let maxCount = 0;
    for (const [s, count] of structureCounts) {
      if (count > maxCount) { maxCount = count; structure = s; }
    }
  }

  const formalityBand: CommunicationBrief["style"]["formality"] =
    formality < 0.35 ? "casual" : formality > 0.65 ? "formal" : "moderate";

  const lengthBand: CommunicationBrief["style"]["length"] =
    avgWords < 15 ? "terse" : avgWords > 40 ? "verbose" : "moderate";

  return { formality: formalityBand, length: lengthBand, structure, usesJargon: jargon, usesEmoji: emoji };
}

function buildPatternsSection(
  profile: PersonProfile | null,
  dynamicsMemories: SynapRecallMemory[],
  engagementMemories: SynapRecallMemory[]
): CommunicationBrief["patterns"] {
  const leadsConversation = (profile?.leaderRate ?? 0.5) > 0.5;

  // Deepens topics: check if any dynamics show built_on_previous
  let deepensCount = 0;
  let dynamicsTotal = 0;
  for (const mem of dynamicsMemories) {
    const parsed = parseDynamicsContent(mem.content);
    if (!parsed) continue;
    dynamicsTotal++;
    if (parsed.builtOnPrevious) deepensCount++;
  }
  const deepensTopics = dynamicsTotal > 0 ? deepensCount / dynamicsTotal > 0.4 : false;

  // Sentiment from engagement signals
  const sentiments: number[] = [];
  for (const mem of engagementMemories) {
    const parsed = parseEngagementContent(mem.content);
    if (parsed) sentiments.push(parsed.sentiment);
  }

  const avgSentiment =
    sentiments.length > 0
      ? sentiments.reduce((a, b) => a + b, 0) / sentiments.length
      : profile?.avgSentiment ?? 0.5;

  // Trend: compare recent batch sentiment against profile's running average
  let sentimentTrend: CommunicationBrief["patterns"]["sentimentTrend"] = "stable";
  if (sentiments.length >= 3 && profile?.avgSentiment !== undefined) {
    const delta = avgSentiment - profile.avgSentiment;
    if (delta > 0.1) sentimentTrend = "warming";
    else if (delta < -0.1) sentimentTrend = "cooling";
  }

  return { leadsConversation, deepensTopics, avgSentiment, sentimentTrend };
}

/** Raw signal patterns that should never appear in observation content. */
const RAW_SIGNAL_PATTERNS = [
  /^formality:\s/,
  /^topic:\s/,
  /^leading:\s/,
  /^sentiment:\s[\d.]+\s*\(/,
  /^response latency:/,
];

function buildObservationsSection(memories: SynapRecallMemory[]): string[] {
  return memories
    .map((m) => m.content)
    .filter((c) => c.length > 0 && !RAW_SIGNAL_PATTERNS.some((p) => p.test(c)))
    .slice(0, 5);
}

async function buildCohortSection(
  profile: PersonProfile | null
): Promise<CommunicationBrief["cohortPriors"]> {
  if (!profile || !profile.cohorts.length || profile.interactionCount >= 30) {
    return { active: false, weight: 0, priors: [] };
  }

  const weight = 1 / (1 + profile.interactionCount / 10);

  const priors = await loadCohortPriors(
    profile.cohorts,
    profile.interactionCount
  );

  return { active: priors.length > 0, weight, priors };
}

// ---------------------------------------------------------------------------
// Main assembly
// ---------------------------------------------------------------------------

/** Flatten vivid + associated tiers (reconstructed excluded — lower confidence). */
function flattenMemories(result: {
  memories: {
    vivid: SynapRecallMemory[];
    associated: SynapRecallMemory[];
    reconstructed: SynapRecallMemory[];
  };
}): SynapRecallMemory[] {
  const vivid = result.memories?.vivid ?? [];
  const associated = result.memories?.associated ?? [];
  return [...vivid, ...associated];
}

export async function assembleBrief(
  personId: string
): Promise<CommunicationBrief> {
  const start = Date.now();

  // 6 parallel Synap queries + profile load — single wave
  const catchSynap = (label: string) => (err: unknown) => {
    const msg = err instanceof Error ? err.message : String(err);
    console.error(`[brief] ${label} failed for ${personId.slice(0, 8)}…: ${msg}`);
    return null;
  };

  const [topicResult, styleResult, dynamicsResult, observationResult, engagementResult, profile] =
    await Promise.all([
      recallByTag("topics discussed", ["signal:topic", `person:${personId}`], 20, 0.2).catch(catchSynap("topic recall")),
      recallByTag("communication style", ["signal:style", `person:${personId}`], 10, 0.2).catch(catchSynap("style recall")),
      recallByTag("conversation dynamics", ["signal:dynamics", `person:${personId}`], 10, 0.2).catch(catchSynap("dynamics recall")),
      recallByTag("communication patterns", ["observation", `person:${personId}`], 5, 0.3).catch(catchSynap("observation recall")),
      recallByTag("engagement signals", ["signal:engagement", `person:${personId}`], 10, 0.2).catch(catchSynap("engagement recall")),
      loadProfile(personId).catch(catchSynap("profile load")),
    ]);

  // Cohort priors chain off profile (needs interactionCount)
  const cohortPriors = await buildCohortSection(profile);

  const topicMemories = topicResult ? flattenMemories(topicResult) : [];
  const styleMemories = styleResult ? flattenMemories(styleResult) : [];
  const dynamicsMemories = dynamicsResult
    ? flattenMemories(dynamicsResult)
    : [];
  const observationMemories = observationResult
    ? flattenMemories(observationResult)
    : [];
  const engagementMemories = engagementResult
    ? flattenMemories(engagementResult)
    : [];

  const brief: CommunicationBrief = {
    personId,
    interactionCount: profile?.interactionCount ?? 0,
    assembledAt: Date.now(),
    topics: buildTopicsSection(topicMemories),
    style: buildStyleSection(profile, styleMemories),
    patterns: buildPatternsSection(profile, dynamicsMemories, engagementMemories),
    observations: buildObservationsSection(observationMemories),
    cohortPriors,
    assemblyMs: Date.now() - start,
  };

  console.log(
    `[brief] assembled for ${personId.slice(0, 8)}… in ${brief.assemblyMs}ms: ` +
      `${brief.topics.hot.length} hot topics, ` +
      `style=${brief.style.formality}, ` +
      `${brief.observations.length} observations, ` +
      `cohort=${brief.cohortPriors.active ? "active" : "inactive"}`
  );

  return brief;
}
