// Panel-local state variables (ds === 'local').
//
// Definitions live in the interface XML and travel with the panel, but the live
// value is instantiated per browser/panel instance starting from the variable's
// initial value — nothing is persisted server-side and no WebSocket traffic is
// involved. The panel logic (a future feature) and write-capable widgets can
// update these values locally.
//
// This module deliberately depends only on the store primitives and types so it
// can be imported by both stores.ts and ws.ts without creating an import cycle.

import { writable, type Readable, type Writable } from './store';
import type { SignalValue, SignalMeta, StateVar } from './types';
import { parseInitialArray, applySizing, type ArrVal } from './arraypolicy';

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

export function declaredVar(name: string): StateVar | undefined {
  return decls.get(name);
}

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

function valueW(name: string): Writable<SignalValue> {
  let s = valueStores.get(name);
  if (!s) {
    s = writable<SignalValue>(DEFAULT_VALUE);
    valueStores.set(name, s);
  }
  return s;
}

function metaW(name: string): Writable<SignalMeta | null> {
  let s = metaStores.get(name);
  if (!s) {
    s = writable<SignalMeta | null>(null);
    metaStores.set(name, s);
  }
  return s;
}

// coerce parses a state variable's stored string into a live value.
function coerce(v: StateVar): any {
  switch (v.type) {
    case 'bool':
      return v.initial === 'true' || v.initial === '1';
    case 'string':
      return v.initial;
    case 'array':
      return parseInitialArray(v);
    default: {
      const n = parseFloat(v.initial);
      return isNaN(n) ? 0 : n;
    }
  }
}

// initLocalState (re)instantiates every state variable to its initial value and
// publishes metadata. Call this whenever a panel is loaded.
export function initLocalState(vars: StateVar[] | undefined): void {
  for (const v of vars ?? []) {
    decls.set(v.name, v);
    valueW(v.name).set({
      value: coerce(v),
      quality: 'good',
      ts: new Date().toISOString(),
    });
    metaW(v.name).set({
      type: v.type ?? 'number',
      unit: v.unit,
      displayLow: v.low ?? 0,
      displayHigh: v.high ?? 100,
      writable: true,
      elem: v.elem,
      sizing: v.sizing,
      capacity: v.capacity,
    });
  }
}

export function getLocalValueStore(name: string): Readable<SignalValue> {
  return valueW(name);
}

export function getLocalMetaStore(name: string): Readable<SignalMeta | null> {
  return metaW(name);
}

// writeLocalState updates a local variable's live value in place.
// When the declared variable is an array type, the written value is passed
// through applySizing to enforce the declared sizing policy.
export function writeLocalState(name: string, value: any): void {
  const sv = decls.get(name);
  let v = value;
  if (sv?.type === 'array' && Array.isArray(value)) v = applySizing(value as ArrVal[], sv);
  valueW(name).set({ value: v, quality: 'good', ts: new Date().toISOString() });
}
