import type { Character, Encounter, Note, Quest } from '@/lib/schemas';
import {
  resourceSuggestions,
  planningSuggestions,
  combatSuggestions,
  type Suggestion,
} from './advisors';

/**
 * The Signals engine: pure detectors over campaign + combat + character state
 * that emit actionable, one-click proposals. This is the "buttons, not chatbot"
 * layer — it recognizes a situation and offers the fix; the user never has to
 * formulate a request. It builds on the existing advisor detectors and adds
 * kernel-aware ones now that combat/spellcasting state is enforced.
 */

/** A quest whose objectives are all done but is still marked active → offer to complete it. */
export function questSignals(quests: Quest[]): Suggestion[] {
  const out: Suggestion[] = [];
  for (const q of quests) {
    if (q.status !== 'active' || q.objectives.length === 0) continue;
    if (q.objectives.every((o) => o.done)) {
      out.push({
        id: `quest-done-${q.id}`,
        category: 'planning',
        severity: 'info',
        title: `“${q.title}” is finished`,
        detail: 'Every objective is checked off — mark the quest complete?',
        action: { type: 'completeQuest', questId: q.id, label: 'Mark complete' },
      });
    }
  }
  return out;
}

/** Spellcasters who have burned through their slots, and concentration-at-risk nudges. */
export function casterSignals(characters: Character[]): Suggestion[] {
  const out: Suggestion[] = [];
  for (const c of characters) {
    if (c.kind !== 'pc' || c.hp.current <= 0) continue;

    const slots = c.spellcasting.slots.filter((s) => s.max > 0);
    if (slots.length > 0 && slots.every((s) => s.current === 0)) {
      out.push({
        id: `no-slots-${c.id}`,
        category: 'resources',
        severity: 'warn',
        title: `${c.name} is out of spell slots`,
        detail: 'No slots remaining — a rest would restore them.',
        action: { type: 'shortRest', characterId: c.id, label: 'Short rest' },
      });
    }

    // Concentration while bloodied is fragile — surface it so the GM keeps it in mind.
    if (c.concentration && c.hp.max > 0 && c.hp.current < c.hp.max * 0.5) {
      out.push({
        id: `conc-risk-${c.id}`,
        category: 'combat',
        severity: 'info',
        title: `${c.name} is concentrating on ${c.concentration.spellName} while bloodied`,
        detail: 'Damage will force a Constitution save — watch for it.',
      });
    }
  }
  return out;
}

const SEVERITY_RANK: Record<Suggestion['severity'], number> = { danger: 0, warn: 1, info: 2 };

export interface BuildSignalsInput {
  characters: Character[];
  notes: Note[];
  quests: Quest[];
  activeEncounter?: Encounter | undefined;
}

/**
 * Aggregate every detector into one severity-sorted, de-duplicated list — the
 * single source the ambient signals UI and the Assistant page both render.
 */
export function buildSignals(input: BuildSignalsInput): Suggestion[] {
  const all = [
    ...combatSuggestions(input.activeEncounter),
    ...casterSignals(input.characters),
    ...resourceSuggestions(input.characters),
    ...questSignals(input.quests),
    ...planningSuggestions(input.notes, input.quests),
  ];
  const seen = new Set<string>();
  const deduped = all.filter((s) => (seen.has(s.id) ? false : (seen.add(s.id), true)));
  return deduped.sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]);
}
