import type { NodeGroup } from './nodeGroups';
export type { NodeGroup };

// Core signal reference: identifies a signal by datasource + name
export interface SignalRef {
  ds: string;
  name: string;
}

// Live value for a signal
export interface SignalValue {
  value: any;
  quality: 'good' | 'uncertain' | 'bad' | 'unknown';
  ts: string | null;
  // Raw EPICS alarm fields (absent/0 = NO_ALARM). severity: 1=MINOR,2=MAJOR,3=INVALID.
  severity?: number;
  status?: number;
}

// Metadata for a signal (received once on subscribe)
export interface SignalMeta {
  type: string;
  unit?: string;
  displayLow: number;
  displayHigh: number;
  writable: boolean;
  enumStrings?: string[];
  description?: string;
  properties?: Record<string, string>;
  tags?: string[];
  // array-local extra fields (present when type === 'array'):
  elem?: 'number' | 'bool' | 'array';
  sizing?: 'dynamic' | 'capped' | 'fixed';
  capacity?: number;
}

// A single widget in an interface
export interface Widget {
  id: string;
  type: string;
  x: number;
  y: number;
  w: number;
  h: number;
  signals: Array<SignalRef & { color?: string }>;
  options: Record<string, string>;
}

// A single data point returned from a history request
export interface HistoryPoint {
  ts: string;
  value: any;
}

// A node in the user-defined signal group tree.
// Folders contain children (other folders or signals); signals are leaf nodes.
export type GroupNode =
  | { kind: 'folder'; id: string; label: string; children: GroupNode[] }
  | { kind: 'signal'; ds: string; name: string };

// Recursive split-layout tree for plot panels. Leaves reference a plot widget
// by id; splits divide their container into child `a` (fraction `ratio`) and
// child `b` (the remainder). dir 'h' = left/right (vertical divider), 'v' =
// top/bottom (horizontal divider).
export type PlotLayout =
  | { type: 'leaf'; widget: string }
  | { type: 'split'; dir: 'h' | 'v'; ratio: number; a: PlotLayout; b: PlotLayout };

// A panel-local state variable. Its definition travels with the interface XML,
// but its live value is instantiated per browser/panel instance starting from
// `initial` (the value is NOT persisted server-side). Referenced as a signal
// via ds 'local'.
export interface StateVar {
  name: string;
  type?: 'number' | 'bool' | 'string' | 'array'; // default 'number'
  initial: string;                     // initial value, stored as a string
  unit?: string;
  low?: number;
  high?: number;
  // array-only (present when type === 'array'):
  elem?: 'number' | 'bool' | 'array';
  sizing?: 'dynamic' | 'capped' | 'fixed';
  capacity?: number;
}

// ── Panel logic (Node-RED–style flow graph) ──────────────────────────────────

// The kind of a logic node.
//
// Triggers are flow entry points:
//   trigger.button    — fired when a button widget's Action names this node
//                       (params: name).
//   trigger.threshold — fired on the rising edge of (signal `op` value)
//                       (params: signal, op, value).
//   trigger.timer     — fired every `interval` ms while the panel is in view
//                       (params: interval).
//   trigger.loop      — fired once on panel load, then every `interval` ms
//                       (params: interval).
//   trigger.change    — fired whenever a signal's value changes
//                       (params: signal).
//   trigger.start     — fired once when the panel is opened (no params).
//   trigger.stop      — fired once when the panel is closed (no params).
//
// Gates combine/guard flow:
//   gate.and          — passes to its output only when ALL incoming triggers
//                       are currently satisfied (no params).
//
// Control flow (use labelled output ports on the wires):
//   flow.if           — evaluate `cond`; continue on the 'then' or 'else' port
//                       (params: cond).
//   flow.loop         — repeat the 'body' port then continue on 'done'
//                       (params: mode 'count'|'while', count, cond).
//
// Actions:
//   action.write      — evaluate `expr` and write the result to `target`
//                       ("ds:name" or a bare local var) (params: target, expr).
//   action.delay      — pause `ms` before continuing downstream (params: ms).
//   action.accumulate — evaluate `expr` and append the value (with a timestamp)
//                       to the named in-memory data array (params: array, expr).
//   action.export     — download one or more named arrays as a CSV file, one
//                       column per array (params: columns = JSON list of
//                       {array,label}, align 'common'|'any'|'interpolate',
//                       filename). Legacy single-array nodes use `array`.
//   action.clear      — empty the named array (params: array).
//   action.log        — evaluate `expr` and log it to the browser console for
//                       debugging a flow (params: expr, label).
//   action.widget     — drive a panel widget by id (params: widget, op). `op` is
//                       one of enable/disable (interaction), show/hide
//                       (visibility), or for plot widgets clearplot (one-shot
//                       buffer clear) / pauseplot / resumeplot (live stream).
//   action.dialog.info  — show an info dialog and wait for the user to dismiss
//                       it before continuing (params: title, message, fields).
//   action.dialog.error — like dialog.info but styled as an error (params:
//                       title, message, fields).
//   action.dialog.setpoint — prompt the user for one or more values; on OK write
//                       each input to its target and continue, on Cancel abort
//                       the flow (params: title, message, fields).
//   All dialogs may carry a `fields` param: a JSON list of
//   {type:'input'|'display', label, target, default, expr}. `input` fields
//   prompt for a value written to `target`; `display` fields show the resolved
//   `expr`. Legacy set-point nodes use `target`/`default` for a single input.
//   Dialog `message`/`title` text may embed signal values as {ds:name} tokens.
//
// Expressions (cond/expr) may reference signals inline as {ds:name} and panel-
// local state variables as bare identifiers. See lib/expr.ts.
export type LogicNodeKind =
  | 'trigger.button'
  | 'trigger.threshold'
  | 'trigger.timer'
  | 'trigger.loop'
  | 'trigger.change'
  | 'trigger.start'
  | 'trigger.stop'
  | 'gate.and'
  | 'flow.if'
  | 'flow.loop'
  | 'action.write'
  | 'action.delay'
  | 'action.accumulate'
  | 'action.export'
  | 'action.clear'
  | 'action.log'
  | 'action.widget'
  | 'action.dialog.info'
  | 'action.dialog.error'
  | 'action.dialog.setpoint'
  | 'action.config.apply'
  | 'action.config.read'
  | 'action.config.write'
  | 'action.config.create'
  | 'action.config.snapshot';

// A block on the flow canvas. `params` holds kind-specific fields as strings.
export interface LogicNode {
  id: string;
  kind: LogicNodeKind;
  x: number;
  y: number;
  params: Record<string, string>;
}

// A directed connection from one node's output to another node's input.
// `fromPort` selects which labelled output it leaves (default 'out'); flow.if
// uses 'then'/'else' and flow.loop uses 'body'/'done'.
export interface LogicWire {
  from: string;     // source node id
  fromPort?: string; // source output port (default 'out')
  to: string;       // target node id
}

// The panel's complete logic flow.
export interface LogicGraph {
  nodes: LogicNode[];
  wires: LogicWire[];
  groups?: NodeGroup[];
}

// A complete HMI interface definition
export interface Interface {
  id: string;
  name: string;
  version: number;
  w: number;
  h: number;
  widgets: Widget[];
  // 'panel' (default) = free-form canvas; 'plot' = split-layout plot panel.
  kind?: 'panel' | 'plot';
  // Present only when kind === 'plot': the viewport split tree.
  layout?: PlotLayout;
  // Panel-local state variable definitions (live value instantiated client-side).
  statevars?: StateVar[];
  // Panel logic flow graph (run client-side in view mode).
  logic?: LogicGraph;
}

// ── Access control ───────────────────────────────────────────────────────────

// Global access level for the current user, as reported by /api/v1/me.
//   'write'    = full access (the default for any non-blacklisted user)
//   'readonly' = may view but not create/modify/delete panels or write signals
//   'none'     = no access
export type AccessLevel = 'none' | 'readonly' | 'write';

// Identity + access info for the calling user (from /api/v1/me).
export interface Me {
  user: string;
  level: AccessLevel;
  groups: string[];
  // Whether the user may add/edit panel logic and server-side control logic.
  // False only when a logic-editors allowlist is configured and excludes them.
  canEditLogic: boolean;
  // Whether the user may view the audit log. False only when an audit-readers
  // allowlist is configured and excludes them, or auditing is disabled.
  canViewAudit: boolean;
  // Whether the user may use the admin pane (manage users/groups/access and view
  // server statistics). False only when an admins allowlist is configured and
  // excludes them.
  canAdmin: boolean;
  // Base UI scale multiplier configured server-side ([ui] default_zoom), applied
  // when this browser has no saved zoom override. 1 means no scaling.
  defaultZoom: number;
}

// Effective permission a user holds on a single panel or folder.
//   'none'  = invisible
//   'read'  = may view
//   'write' = may view and modify
export type Perm = 'none' | 'read' | 'write';

// A share grant: gives a single user or a named user-group read/write access to
// a panel or folder.
export interface Grant {
  kind: 'user' | 'group';
  name: string;
  perm: 'read' | 'write';
}

// Sharing record for a panel (from GET /api/v1/interfaces/{id}/acl).
export interface PanelACL {
  owner: string;
  folder: string;
  public: '' | 'read' | 'write';
  grants: Grant[];
  perm: Perm; // the caller's own effective permission
}

// A folder in the panel-organisation hierarchy (from GET /api/v1/folders).
export interface Folder {
  id: string;
  name: string;
  parent: string;
  owner: string;
  public?: '' | 'read' | 'write';
  grants?: Grant[];
  perm: Perm; // the caller's own effective permission
}

// An entry in the interface list (GET /api/v1/interfaces), enriched with the
// owner, containing folder, and the caller's permission.
export interface InterfaceListItem {
  id: string;
  name: string;
  version: number;
  // 'plot' for split-layout plot panels; absent/'panel' for free-form panels.
  kind?: 'panel' | 'plot';
  owner?: string;
  folder?: string;
  order?: number;
  perm: Perm;
  // Derived visibility bucket (private|group|global) + the groups a group-scoped
  // panel is shared with, used by the Mine/Group/Global selector filter.
  scope?: string;
  groups?: string[];
}

// ── Synthetic signals ────────────────────────────────────────────────────────

export interface InputRef {
  ds: string;
  signal: string;
}

export interface PipelineNode {
  type: string;
  params: Record<string, any>;
}

// DAG form of a synthetic signal (mirrors backend synthetic.Graph). A node is a
// source (ds:signal root), an op (DSP node with ordered `inputs`), or the single
// output. When present it supersedes the legacy inputs+pipeline linear form.
export type SynthNodeKind = 'source' | 'op' | 'output';
export interface SynthGraphNode {
  id: string;
  kind: SynthNodeKind;
  op?: string;
  params?: Record<string, any>;
  ds?: string;
  signal?: string;
  inputs?: string[]; // upstream node ids, in input order
  x?: number;
  y?: number;
}
export interface SynthGraph {
  nodes: SynthGraphNode[];
  output: string;
  groups?: NodeGroup[];
}

export interface SignalDef {
  name: string;
  ds?: string;      // legacy single input
  signal?: string;  // legacy single input
  inputs?: InputRef[];
  pipeline?: PipelineNode[];
  graph?: SynthGraph; // DAG form (preferred when present)
  meta: {
    unit?: string;
    description?: string;
    displayLow?: number;
    displayHigh?: number;
    writable?: boolean;
  };
  // Visibility scope (default 'panel'). 'owner'/'panel' are stamped/filtered
  // server-side; owner is set from the trusted identity on create.
  visibility?: 'panel' | 'user' | 'group' | 'global';
  owner?: string;
  groups?: string[]; // groups for 'group' visibility
  panel?: string; // bound interface id when visibility === 'panel'
}
