"use client";

import { ALERTS_CHANGED_EVENT, notifyAlertsChanged } from "@/lib/alerts/events";
import { useEffect, useMemo, useRef, useState } from "react";
import { createAlertEvaluationPipeline } from "@/lib/alerts/pipeline";
import { createAlertRuleEvaluator } from "@/lib/alerts/rule-evaluator";
import {
  findLayoutIdForAlertId,
  loadAlertRules,
  loadAllAlertRules,
  loadFiredAlertKeys,
  saveAlertRules,
  saveFiredAlertKeys,
} from "@/lib/alerts/storage";
import {
  AlertSoundDispatcher,
  BrowserNotificationDispatcher,
  CompositeNotificationDispatcher,
} from "@/lib/alerts/notifications/dispatcher";
import { unlockAlertAudio } from "@/lib/alerts/play-alert-sound";
import { getLiveMarketClientSession } from "@/lib/market/live/client-session";
import { resolveLiveMarketRequest } from "@/lib/market/live/resolve-request";
import type { AlertRule } from "@/lib/alerts/types";
import type { CanonicalSymbol } from "@/lib/market/instruments";
import type { SupportedTimeframe } from "@/lib/market/timeframes";

type StreamRef = {
  symbol: CanonicalSymbol;
  timeframe: SupportedTimeframe;
};

function uniqueStreams(rules: AlertRule[]): StreamRef[] {
  const seen = new Set<string>();
  const out: StreamRef[] = [];
  for (const r of rules) {
    if (!r.enabled) {
      continue;
    }
    const key = `${r.symbol}|${r.timeframe}`;
    if (seen.has(key)) {
      continue;
    }
    seen.add(key);
    out.push({ symbol: r.symbol, timeframe: r.timeframe });
  }
  return out;
}

/**
 * Off-chart alert evaluation host (not mounted inside TradingChart).
 * Subscribes to the shared live market session and evaluates persisted rules.
 */
export function AlertMarketSessionHost() {
  const firedKeysRef = useRef<Set<string>>(loadFiredAlertKeys());
  const previousCloseRef = useRef(new Map<string, number>());
  const previousTimeRef = useRef(new Map<string, number>());

  const [rules, setRules] = useState(() =>
    loadAllAlertRules().filter((r) => r.enabled),
  );
  useEffect(() => {
    const onChange = () => setRules(loadAllAlertRules().filter((r) => r.enabled));
    window.addEventListener(ALERTS_CHANGED_EVENT, onChange);
    return () => window.removeEventListener(ALERTS_CHANGED_EVENT, onChange);
  }, []);

  useEffect(() => {
    const unlock = () => unlockAlertAudio();
    window.addEventListener("pointerdown", unlock, { once: true });
    window.addEventListener("keydown", unlock, { once: true });
    return () => {
      window.removeEventListener("pointerdown", unlock);
      window.removeEventListener("keydown", unlock);
    };
  }, []);
  const rulesById = useMemo(() => new Map(rules.map((r) => [r.id, r])), [rules]);

  useEffect(() => {
    if (rules.length === 0) {
      return;
    }
    const firedKeys = firedKeysRef.current;
    const evaluator = createAlertRuleEvaluator({
      rules,
      firedKeys,
      onFiredKey: (key) => {
        firedKeys.add(key);
        saveFiredAlertKeys(firedKeys);
      },
    });
    const dispatcher = new CompositeNotificationDispatcher([
      new AlertSoundDispatcher(),
      new BrowserNotificationDispatcher(),
    ]);
    const pipeline = createAlertEvaluationPipeline({
      evaluator,
      dispatcher,
      rulesById,
      previousCloseByStream: previousCloseRef.current,
      previousTimeByStream: previousTimeRef.current,
      onEvents: (events) => {
        let changed = false;
        for (const ev of events) {
          const layoutId = findLayoutIdForAlertId(ev.ruleId);
          if (!layoutId) {
            continue;
          }
          const layoutRules = loadAlertRules(layoutId);
          const idx = layoutRules.findIndex((r) => r.id === ev.ruleId);
          if (idx < 0) {
            continue;
          }
          const rule = layoutRules[idx]!;
          layoutRules[idx] = {
            ...rule,
            lastTriggeredAt: ev.observedAt,
            updatedAt: ev.observedAt,
            enabled: rule.oneShot ? false : rule.enabled,
          };
          saveAlertRules(layoutId, layoutRules);
          changed = true;
        }
        if (changed) {
          notifyAlertsChanged();
        }
      },
    });

    const session = getLiveMarketClientSession();
    const unsubs: (() => void)[] = [];
    for (const stream of uniqueStreams(rules)) {
      const request = resolveLiveMarketRequest(stream.symbol, stream.timeframe);
      if (!request) {
        continue;
      }
      unsubs.push(
        session.subscribe(request, (update) => {
          if (!update.snapshot) {
            return;
          }
          pipeline.processMarketEvent({
            snapshot: update.snapshot,
            lastPrice: update.snapshot.forming?.close ?? update.snapshot.completedTail.at(-1)?.close ?? 0,
          });
        }),
      );
    }
    return () => {
      for (const u of unsubs) {
        u();
      }
    };
  }, [rules, rulesById]);

  return null;
}
