import { readable, writable, type Readable } from './store';
import { wsClient } from './ws';
import { getLocalValueStore, getLocalMetaStore } from './localstate';
import type { SignalRef, SignalValue, SignalMeta } from './types';

// ── Default values ────────────────────────────────────────────────────────────

const DEFAULT_VALUE: SignalValue = {
  value: null,
  quality: 'unknown',
  ts: null,
};

// ── History buffer ────────────────────────────────────────────────────────────

const HISTORY_MAX = 1000;

export interface HistoryPoint {
  ts: string;
  value: any;
}

const historyBuffers = new Map<string, HistoryPoint[]>();

function pushHistory(key: string, v: SignalValue): void {
  if (v.value === null || v.value === undefined || v.ts === null) return;
  let buf = historyBuffers.get(key);
  if (!buf) {
    buf = [];
    historyBuffers.set(key, buf);
  }
  buf.push({ ts: v.ts, value: v.value });
  if (buf.length > HISTORY_MAX) buf.splice(0, buf.length - HISTORY_MAX);
}

export function getSignalHistory(ref: SignalRef): HistoryPoint[] {
  const key = `${ref.ds}\0${ref.name}`;
  return historyBuffers.get(key) ?? [];
}

// ── Store maps (keyed by "ds\0name") ─────────────────────────────────────────

const valueStores = new Map<string, Readable<SignalValue>>();
const metaStores = new Map<string, Readable<SignalMeta | null>>();

function refKey(ref: SignalRef): string {
  return `${ref.ds}\0${ref.name}`;
}

/**
 * Returns a Svelte readable store that delivers live signal values.
 * Created lazily; the WS subscription is started on first use.
 * The store stays alive once created (simplest correct behaviour for Phase 4).
 */
export function getSignalStore(ref: SignalRef): Readable<SignalValue> {
  // Panel-local variables are served entirely client-side (no WS subscription).
  if (ref.ds === 'local') return getLocalValueStore(ref.name);

  const key = refKey(ref);
  const existing = valueStores.get(key);
  if (existing) return existing;

  const store = readable<SignalValue>(DEFAULT_VALUE, (set) => {
    // metaStore may already exist; we need a shared WS subscription.
    // We use a shared subscription via _createShared.
    const unsub = _getShared(ref).subscribeValue(set);
    return unsub;
  });

  valueStores.set(key, store);
  return store;
}

/**
 * Returns a Svelte readable store that delivers signal metadata.
 * Metadata is sent once by the server on subscribe.
 */
export function getMetaStore(ref: SignalRef): Readable<SignalMeta | null> {
  if (ref.ds === 'local') return getLocalMetaStore(ref.name);

  const key = refKey(ref);
  const existing = metaStores.get(key);
  if (existing) return existing;

  const store = readable<SignalMeta | null>(null, (set) => {
    const unsub = _getShared(ref).subscribeMeta(set);
    return unsub;
  });

  metaStores.set(key, store);
  return store;
}

// ── Shared subscription helper ────────────────────────────────────────────────
//
// Both getSignalStore and getMetaStore need to share one WS subscription per
// signal. This object manages that sharing.

interface SharedEntry {
  subscribeValue: (set: (v: SignalValue) => void) => () => void;
  subscribeMeta: (set: (m: SignalMeta | null) => void) => () => void;
}

const sharedEntries = new Map<string, SharedEntry>();

function _getShared(ref: SignalRef): SharedEntry {
  const key = refKey(ref);
  const existing = sharedEntries.get(key);
  if (existing) return existing;

  // Internal writables that fan out to all store subscribers
  const valueW = writable<SignalValue>(DEFAULT_VALUE);
  const metaW = writable<SignalMeta | null>(null);

  // Reference count for the WS subscription
  let wsUnsub: (() => void) | null = null;
  let refCount = 0;

  function addRef() {
    if (refCount === 0) {
      wsUnsub = wsClient.subscribe(
        ref,
        (v) => {
          valueW.set(v);
          pushHistory(key, v);
        },
        (m) => metaW.set(m),
      );
    }
    refCount++;
  }

  function removeRef() {
    refCount--;
    if (refCount <= 0 && wsUnsub) {
      wsUnsub();
      wsUnsub = null;
      refCount = 0;
    }
  }

  const entry: SharedEntry = {
    subscribeValue(set) {
      addRef();
      const unsubStore = valueW.subscribe(set);
      return () => {
        unsubStore();
        removeRef();
      };
    },
    subscribeMeta(set) {
      addRef();
      const unsubStore = metaW.subscribe(set);
      return () => {
        unsubStore();
        removeRef();
      };
    },
  };

  sharedEntries.set(key, entry);
  return entry;
}
