/**
 * @Orchestrator — thin backend-delegation layer (v3).
 *
 * All AI intelligence lives on the backend. The Electron process:
 *  1. Checks connectivity + auth status
 *  2. Delegates to BackendClient.sendHomeRequest() / sendFloatingRequest()
 *     which handle the WS lifecycle, tool-call ↔ DrizzleExecutor round-trips,
 *     and v3 stream event dispatch.
 *  3. Forwards v3 typed stream frames to the renderer via IPC.
 */

import { BrowserWindow } from 'electron';
import { getBackendClient, OfflineError, AuthExpiredError } from '../api/backend-client';
import { getAuthManager } from '../auth/auth-manager';
import { getStore } from '../store';
import type { WsFloatingRequest } from '../../shared/api-types';

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const AI_STREAM_CHANNEL = 'ai:stream';

const BRIEF_SCHEDULER_INTERVAL_MS = 60_000;
const BRIEF_REGEN_DEBOUNCE_MS = 1_500;
const NOOP = () => undefined;

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface OrchestrateInput {
  message: string;
  requestId?: string;
  sessionId?: string;
  conversationHistory?: Array<{ role: 'user' | 'assistant' | 'system'; content: string }>;
  sender?: Electron.WebContents;
}

interface OrchestrateFloatingInput {
  message: string;
  requestId?: string;
  sessionId?: string;
  scope: WsFloatingRequest['scope'];
  conversationHistory?: WsFloatingRequest['conversationHistory'];
  briefMode?: boolean;
  briefingContext?: string;
  sender?: Electron.WebContents;
}

interface TaskBriefResearchInput {
  taskId: string;
  requestId?: string;
  sender?: Electron.WebContents;
  onComplete: (result: { briefingMarkdown: string; canvasDraft?: string; canvasKind?: string }) => void;
}

interface OrchestrateResult {
  response: string;
  error?: string;
}

let briefSchedulerTimer: ReturnType<typeof setInterval> | null = null;
let briefRegenerationTimer: ReturnType<typeof setTimeout> | null = null;
let lastGeneratedSlotKey: string | null = null;

// ---------------------------------------------------------------------------
// IPC helper
// ---------------------------------------------------------------------------

function sendFrame(sender: Electron.WebContents | undefined, payload: Record<string, unknown>): void {
  if (!sender || sender.isDestroyed()) return;
  sender.send(AI_STREAM_CHANNEL, payload);
}

// ---------------------------------------------------------------------------
// Connectivity + auth guard (shared)
// ---------------------------------------------------------------------------

async function checkConnectivity(): Promise<{ ok: true } | { ok: false; error: string }> {
  const client = getBackendClient();
  const online = await client.isOnline();
  if (!online) {
    return { ok: false, error: 'You are offline. AI features require an internet connection.' };
  }
  const authenticated = await getAuthManager().isAuthenticated();
  if (!authenticated) {
    return { ok: false, error: 'Please log in to use AI features.' };
  }
  return { ok: true };
}

// ---------------------------------------------------------------------------
// Orchestrate — Home chat (public entry point)
// ---------------------------------------------------------------------------

export async function orchestrate(input: OrchestrateInput): Promise<OrchestrateResult> {
  const { message, requestId, sessionId, conversationHistory, sender } = input;

  const check = await checkConnectivity();
  if (!check.ok) return { response: '', error: check.error };

  try {
    const client = getBackendClient();
    const { requestId: activeRequestId, promise } = client.sendHomeRequest(message, conversationHistory, requestId, sessionId, {
      onStart: () => sendFrame(sender, { type: 'stream_start', requestId: activeRequestId }),
      onText: (chunk) => sendFrame(sender, { type: 'stream_text', requestId: activeRequestId, chunk }),
      onEnd: (mutations) => sendFrame(sender, { type: 'stream_end', requestId: activeRequestId, mutations: mutations as unknown[] | undefined }),
      onError: () => sendFrame(sender, { type: 'stream_end', requestId: activeRequestId }),
    });

    await promise;
    return { response: 'ok' };
  } catch (err) {
    sendFrame(sender, { type: 'stream_end', requestId: '' });

    if (err instanceof OfflineError) {
      return { response: '', error: 'You are offline. AI features require an internet connection.' };
    }
    if (err instanceof AuthExpiredError) {
      return { response: '', error: 'Your session has expired. Please log in again.' };
    }

    const errMsg = err instanceof Error ? err.message : String(err);
    return { response: '', error: errMsg };
  }
}

// ---------------------------------------------------------------------------
// Orchestrate Floating — Floating chat (public entry point)
// ---------------------------------------------------------------------------

export async function orchestrateFloating(input: OrchestrateFloatingInput): Promise<OrchestrateResult> {
  const { message, requestId, sessionId, scope, conversationHistory, briefMode, briefingContext, sender } = input;

  const check = await checkConnectivity();
  if (!check.ok) return { response: '', error: check.error };

  try {
    const client = getBackendClient();
    const { requestId: activeRequestId, promise } = client.sendFloatingRequest(message, scope, conversationHistory, requestId, sessionId, briefMode, briefingContext, {
      onStart: () => sendFrame(sender, { type: 'stream_start', requestId: activeRequestId }),
      onText: (chunk) => sendFrame(sender, { type: 'stream_text', requestId: activeRequestId, chunk }),
      onEnd: (mutations) => sendFrame(sender, { type: 'stream_end', requestId: activeRequestId, mutations: mutations as unknown[] | undefined }),
      onDomain: (domain) => sendFrame(sender, { type: 'floating_domain', requestId: activeRequestId, domain }),
      onError: () => sendFrame(sender, { type: 'stream_end', requestId: activeRequestId }),
    });

    await promise;
    return { response: 'ok' };
  } catch (err) {
    sendFrame(sender, { type: 'stream_end', requestId: '' });

    if (err instanceof OfflineError) {
      return { response: '', error: 'You are offline. AI features require an internet connection.' };
    }
    if (err instanceof AuthExpiredError) {
      return { response: '', error: 'Your session has expired. Please log in again.' };
    }

    const errMsg = err instanceof Error ? err.message : String(err);
    return { response: '', error: errMsg };
  }
}

// ---------------------------------------------------------------------------
// Task Brief Research
// ---------------------------------------------------------------------------

export async function orchestrateTaskBriefResearch(input: TaskBriefResearchInput): Promise<OrchestrateResult> {
  const { taskId, requestId, sender, onComplete } = input;

  const check = await checkConnectivity();
  if (!check.ok) return { response: '', error: check.error };

  let fullText = '';

  try {
    const client = getBackendClient();
    const activeRequestId = requestId ?? crypto.randomUUID();
    const { promise } = client.sendTaskBriefRequest(taskId, activeRequestId, undefined, {
      onStart: () => sendFrame(sender, { type: 'stream_start', requestId: activeRequestId }),
      onText: (chunk) => {
        fullText += chunk;
        sendFrame(sender, { type: 'stream_text', requestId: activeRequestId, chunk });
      },
      onEnd: (mutations) => {
        const canvasMutation = Array.isArray(mutations)
          ? (mutations as Array<Record<string, unknown>>).find((m) => m.type === 'canvas_draft')
          : null;
        const briefingMarkdown = fullText;
        const canvasDraft = canvasMutation?.content as string | undefined;
        const canvasKind = canvasMutation?.kind as string | undefined;
        onComplete({ briefingMarkdown, canvasDraft, canvasKind });
        sendFrame(sender, { type: 'stream_end', requestId: activeRequestId, mutations: mutations as unknown[] | undefined });
      },
      onError: () => sendFrame(sender, { type: 'stream_end', requestId: activeRequestId }),
    });

    await promise;
    return { response: 'ok' };
  } catch (err) {
    sendFrame(sender, { type: 'stream_end', requestId: '' });
    if (err instanceof OfflineError) return { response: '', error: 'You are offline.' };
    if (err instanceof AuthExpiredError) return { response: '', error: 'Your session has expired. Please log in again.' };
    return { response: '', error: err instanceof Error ? err.message : String(err) };
  }
}

// ---------------------------------------------------------------------------
// Daily Brief
// ---------------------------------------------------------------------------

function todayString(): string {
  return new Date().toISOString().slice(0, 10);
}

function getBriefTimeSlot(now: Date): string {
  const minutes = now.getHours() * 60 + now.getMinutes();
  if (minutes < 6 * 60) return 'night';
  if (minutes < 9 * 60) return 'morning';
  if (minutes < 11 * 60 + 30) return 'mid_morning';
  if (minutes < 14 * 60) return 'lunch';
  if (minutes < 17 * 60) return 'afternoon';
  if (minutes < 21 * 60) return 'evening';
  return 'night';
}

function getCurrentSlotKey(now = new Date()): string {
  return `${todayString()}:${getBriefTimeSlot(now)}`;
}

function markCurrentSlotAsGenerated(): void {
  lastGeneratedSlotKey = getCurrentSlotKey();
}

/** Returns cached brief content if it was generated today, otherwise null. */
export function getCachedBrief(): string | null {
  const cache = getStore().get('dailyBriefCache');
  if (!cache || cache.date !== todayString()) return null;
  return cache.content;
}

/** Invalidate the cache so the next home visit triggers a fresh generation. */
export function invalidateBriefCache(): void {
  getStore().set('dailyBriefCache', null);
  scheduleBriefRegeneration();
}

function scheduleBriefRegeneration(delayMs = BRIEF_REGEN_DEBOUNCE_MS): void {
  if (briefRegenerationTimer) {
    clearTimeout(briefRegenerationTimer);
  }

  briefRegenerationTimer = setTimeout(() => {
    briefRegenerationTimer = null;
    void generateAndCacheBrief();
  }, delayMs);
}

/** Regenerate the brief silently in background, cache it, then push to all windows. */
export async function generateAndCacheBrief(): Promise<boolean> {
  const check = await checkConnectivity();
  if (!check.ok) return false;

  let content = '';
  try {
    const client = getBackendClient();
    const { promise } = client.sendBriefRequest('home', undefined, undefined, undefined, {
      onStart: NOOP,
      onText: (chunk) => { content += chunk; },
      onEnd: NOOP,
      onError: NOOP,
    });
    await promise;
  } catch {
    return false;
  }

  if (!content) return false;

  getStore().set('dailyBriefCache', { content, date: todayString() });
  markCurrentSlotAsGenerated();

  for (const win of BrowserWindow.getAllWindows()) {
    if (!win.isDestroyed()) {
      win.webContents.send('ai:brief-updated', content);
    }
  }

  return true;
}

export function startBriefScheduler(): void {
  if (briefSchedulerTimer) return;

  const tick = async () => {
    if (BrowserWindow.getAllWindows().length === 0) return;
    const slotKey = getCurrentSlotKey();
    if (slotKey === lastGeneratedSlotKey) return;
    const generated = await generateAndCacheBrief();
    if (!generated) return;
  };

  briefSchedulerTimer = setInterval(() => {
    void tick();
  }, BRIEF_SCHEDULER_INTERVAL_MS);

  // Run once on startup so the current slot has a fresh brief immediately.
  void tick();
}

export function stopBriefScheduler(): void {
  if (briefSchedulerTimer) {
    clearInterval(briefSchedulerTimer);
    briefSchedulerTimer = null;
  }
  if (briefRegenerationTimer) {
    clearTimeout(briefRegenerationTimer);
    briefRegenerationTimer = null;
  }
}

/** Stream the daily brief to the renderer and cache the result. */
export async function dailyBrief(sender?: Electron.WebContents, requestId?: string): Promise<OrchestrateResult> {
  let content = '';

  const check = await checkConnectivity();
  if (!check.ok) return { response: '', error: check.error };

  try {
    const client = getBackendClient();
    const activeRequestId = requestId ?? crypto.randomUUID();
    const { promise } = client.sendBriefRequest('home', undefined, activeRequestId, undefined, {
      onStart: () => sendFrame(sender, { type: 'stream_start', requestId: activeRequestId }),
      onText: (chunk) => {
        content += chunk;
        sendFrame(sender, { type: 'stream_text', requestId: activeRequestId, chunk });
      },
      onEnd: () => sendFrame(sender, { type: 'stream_end', requestId: activeRequestId }),
      onError: () => sendFrame(sender, { type: 'stream_end', requestId: activeRequestId }),
    });
    await promise;
  } catch (err) {
    sendFrame(sender, { type: 'stream_end', requestId: '' });
    if (err instanceof OfflineError) return { response: '', error: 'You are offline.' };
    if (err instanceof AuthExpiredError) return { response: '', error: 'Your session has expired. Please log in again.' };
    return { response: '', error: err instanceof Error ? err.message : String(err) };
  }

  if (content) {
    getStore().set('dailyBriefCache', { content, date: todayString() });
    markCurrentSlotAsGenerated();
  }

  return { response: 'ok' };
}
