// Phase 7 implementation intentions. Rules are persisted in SQLite via
// Tauri commands; this frontend runner binds them to the Phase 6 event
// bus and executes their small local actions.

import { invoke } from "@tauri-apps/api/core";
import type {
  ImplementationRule,
  ImplementationRuleAction,
  ImplementationRuleDraft,
} from "$lib/types/app";
import { hasTauriRuntime } from "$lib/utils/runtime.js";
import { page, settings, tasks } from "$lib/stores/page.svelte.js";
import { toasts } from "$lib/stores/toasts.svelte.js";

const TIME_RULE_POLL_MS = 30_000;
const RULES_CHANGED_EVENT = "lumotia:implementation-rules-changed";

export const implementationRules = $state<ImplementationRule[]>([]);

let loaded = false;
let started = false;
let timePollHandle: ReturnType<typeof setInterval> | null = null;
const firingRules = new Set<string>();

function todayLocalKey(): string {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}

function currentHHMM(): string {
  const d = new Date();
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
}

function hasTimeAlreadyPassed(hhmm: string): boolean {
  return currentHHMM() >= hhmm;
}

export function initialLastFiredKeyForTimeRule(hhmm: string): string | null {
  return hasTimeAlreadyPassed(hhmm) ? `${todayLocalKey()}@${hhmm}` : null;
}

function replaceRule(next: ImplementationRule): void {
  const idx = implementationRules.findIndex((rule) => rule.id === next.id);
  if (idx >= 0) implementationRules[idx] = next;
  else implementationRules.unshift(next);
}

function broadcastRulesChanged(): void {
  if (typeof window === "undefined") return;
  window.dispatchEvent(new CustomEvent(RULES_CHANGED_EVENT));
}

export async function loadImplementationRules(force = false): Promise<void> {
  if (!hasTauriRuntime()) {
    implementationRules.length = 0;
    loaded = true;
    return;
  }
  if (loaded && !force) return;
  const rows = await invoke<ImplementationRule[]>("list_implementation_rules");
  implementationRules.splice(0, implementationRules.length, ...rows);
  loaded = true;
}

export async function createImplementationRule(
  draft: ImplementationRuleDraft,
): Promise<ImplementationRule> {
  const row = await invoke<ImplementationRule>("create_implementation_rule", { request: draft });
  replaceRule(row);
  broadcastRulesChanged();
  return row;
}

export async function setImplementationRuleEnabled(id: string, enabled: boolean): Promise<void> {
  const row = await invoke<ImplementationRule>("set_implementation_rule_enabled", { id, enabled });
  replaceRule(row);
  broadcastRulesChanged();
}

export async function deleteImplementationRule(id: string): Promise<void> {
  await invoke("delete_implementation_rule", { id });
  const idx = implementationRules.findIndex((rule) => rule.id === id);
  if (idx >= 0) implementationRules.splice(idx, 1);
  broadcastRulesChanged();
}

async function markRuleFired(rule: ImplementationRule, firedKey: string): Promise<void> {
  const row = await invoke<ImplementationRule>("mark_implementation_rule_fired", {
    id: rule.id,
    firedKey,
  });
  replaceRule(row);
}

function actionLabel(action: ImplementationRuleAction): string {
  if (action.kind === "speak_line") return `Speak "${action.text}"`;
  if (action.kind === "start_timer") return "Start a 5-minute timer";
  if (action.kind === "surface") {
    if (action.target === "task") return `Surface ${action.label || "task"}`;
    if (action.target === "inbox") return "Surface inbox";
    if (action.target === "today") return "Surface today";
    return "Surface tasks";
  }
  return "Action";
}

export function ruleSummary(rule: ImplementationRule): string {
  const when = rule.triggerKind === "time_of_day"
    ? `At ${rule.triggerValue}`
    : rule.triggerKind === "task_completed"
      ? "After a task completes"
      : "After morning triage";
  return `${when} -> ${rule.actions.map(actionLabel).join(" + ")}`;
}

function surfaceAction(action: Extract<ImplementationRuleAction, { kind: "surface" }>): void {
  if (action.target === "task" && action.taskId) {
    const task = tasks.find((candidate) => candidate.id === action.taskId);
    if (!task) {
      // The rule outlived its target. Don't fake a success toast —
      // surface the full list so the user can still act, and tell
      // them why the specific task isn't there. Cached label used
      // only for identification, not as a claim of surfacing.
      page.current = "tasks";
      page.taskSidebarOpen = true;
      const cachedLabel = action.label?.trim();
      toasts.warn(
        "Rule target missing",
        cachedLabel
          ? `The task "${cachedLabel}" is gone — edit the rule to point at something else.`
          : "That rule's task is gone — edit the rule to point at something else.",
      );
      return;
    }
    page.current = "tasks";
    page.taskSidebarOpen = true;
    toasts.info("Task ready", task.text);
    return;
  }

  page.current = "tasks";
  page.taskSidebarOpen = true;

  if (action.target === "inbox") {
    toasts.info("Inbox is here", "Your task list is open.");
  } else if (action.target === "today") {
    toasts.info("Today's list is here", "Your task list is open.");
  } else {
    toasts.info("Tasks are here", "Your list is open.");
  }
}

async function executeAction(action: ImplementationRuleAction): Promise<void> {
  if (action.kind === "surface") {
    surfaceAction(action);
    return;
  }
  if (action.kind === "start_timer") {
    window.dispatchEvent(new CustomEvent("lumotia:start-timer", {
      detail: {
        taskId: action.taskId ?? null,
        seconds: 300,
        label: action.label ?? "5-minute timer",
      },
    }));
    return;
  }
  if (action.kind === "speak_line") {
    await invoke("tts_speak", {
      text: action.text,
      rate: settings.ttsRate,
      voice: settings.ttsVoice ?? null,
    });
  }
}

async function fireRule(rule: ImplementationRule, firedKey?: string): Promise<void> {
  if (!rule.enabled) return;
  if (settings.nudgesMuted) return;
  if (firedKey && rule.lastFiredKey === firedKey) return;
  if (firingRules.has(rule.id)) return;

  firingRules.add(rule.id);
  try {
    for (const action of rule.actions) {
      try {
        await executeAction(action);
      } catch {
        // Rule actions are assistance, not critical workflow. Keep going
        // so a missing TTS voice does not block surfacing a task.
      }
    }
    if (firedKey) {
      await markRuleFired(rule, firedKey);
    }
  } finally {
    firingRules.delete(rule.id);
  }
}

function timeRuleFiredKey(rule: ImplementationRule): string {
  return `${todayLocalKey()}@${rule.triggerValue}`;
}

async function checkTimeRules(): Promise<void> {
  if (!loaded) await loadImplementationRules();
  for (const rule of implementationRules) {
    if (rule.triggerKind !== "time_of_day") continue;
    if (!rule.enabled) continue;
    if (currentHHMM() < rule.triggerValue) continue;
    const firedKey = timeRuleFiredKey(rule);
    if (rule.lastFiredKey === firedKey) continue;
    await fireRule(rule, firedKey);
  }
}

function onTaskCompleted(): void {
  for (const rule of implementationRules) {
    if (rule.triggerKind === "task_completed") void fireRule(rule);
  }
}

function onMorningTriageFinished(): void {
  for (const rule of implementationRules) {
    if (rule.triggerKind === "morning_triage_finished") void fireRule(rule);
  }
}

function onRulesChanged(): void {
  void checkTimeRules();
}

export function startImplementationIntentions(): void {
  if (started) return;
  if (typeof window === "undefined") return;
  started = true;

  void loadImplementationRules(true)
    .then(() => checkTimeRules())
    .catch(() => {});
  window.addEventListener("lumotia:task-completed", onTaskCompleted);
  window.addEventListener("lumotia:morning-triage-finished", onMorningTriageFinished);
  window.addEventListener(RULES_CHANGED_EVENT, onRulesChanged);
  timePollHandle = setInterval(() => {
    void checkTimeRules().catch(() => {});
  }, TIME_RULE_POLL_MS);
}

export function stopImplementationIntentions(): void {
  if (!started) return;
  started = false;
  window.removeEventListener("lumotia:task-completed", onTaskCompleted);
  window.removeEventListener("lumotia:morning-triage-finished", onMorningTriageFinished);
  window.removeEventListener(RULES_CHANGED_EVENT, onRulesChanged);
  if (timePollHandle !== null) {
    clearInterval(timePollHandle);
    timePollHandle = null;
  }
}
