import type { Interface, Widget, SignalRef, PlotLayout, StateVar, LogicGraph } from './types';

/** Escape a string for use as an XML attribute value. */
function xmlEsc(s: string): string {
  return s
    .replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

/** Serialize a panel-logic flow graph to indented XML lines. */
function serializeLogic(graph: LogicGraph, lines: string[]): void {
  lines.push(`  <logic>`);
  for (const node of graph.nodes) {
    lines.push(
      `    <node id="${xmlEsc(node.id)}" kind="${xmlEsc(node.kind)}" ` +
      `x="${node.x}" y="${node.y}">`
    );
    for (const [key, value] of Object.entries(node.params)) {
      lines.push(`      <param key="${xmlEsc(key)}" value="${xmlEsc(value)}"/>`);
    }
    lines.push(`    </node>`);
  }
  for (const wire of graph.wires) {
    const portAttr = wire.fromPort && wire.fromPort !== 'out'
      ? ` port="${xmlEsc(wire.fromPort)}"` : '';
    lines.push(`    <wire from="${xmlEsc(wire.from)}"${portAttr} to="${xmlEsc(wire.to)}"/>`);
  }
  for (const group of graph.groups ?? []) {
    const collAttr = group.collapsed ? ` collapsed="1"` : '';
    lines.push(`    <group id="${xmlEsc(group.id)}" label="${xmlEsc(group.label)}"${collAttr}>`);
    for (const member of group.members) {
      lines.push(`      <member id="${xmlEsc(member)}"/>`);
    }
    lines.push(`    </group>`);
  }
  lines.push(`  </logic>`);
}

/** Serialize a PlotLayout subtree to indented XML lines. */
function serializeLayout(node: PlotLayout, indent: string, lines: string[]): void {
  if (node.type === 'leaf') {
    lines.push(`${indent}<leaf widget="${xmlEsc(node.widget)}"/>`);
    return;
  }
  lines.push(`${indent}<split dir="${node.dir}" ratio="${node.ratio}">`);
  serializeLayout(node.a, indent + '  ', lines);
  serializeLayout(node.b, indent + '  ', lines);
  lines.push(`${indent}</split>`);
}

/** Serialize an Interface object to an XML string. */
export function serializeInterface(iface: Interface): string {
  const lines: string[] = [];
  lines.push(`<?xml version="1.0" encoding="UTF-8"?>`);
  const kindAttr = iface.kind === 'plot' ? ` kind="plot"` : '';
  lines.push(
    `<interface id="${xmlEsc(iface.id)}" name="${xmlEsc(iface.name)}" ` +
    `version="${iface.version}" width="${iface.w}" height="${iface.h}"${kindAttr}>`
  );
  if (iface.kind === 'plot' && iface.layout) {
    lines.push(`  <layout>`);
    serializeLayout(iface.layout, '    ', lines);
    lines.push(`  </layout>`);
  }
  for (const sv of iface.statevars ?? []) {
    const attrs = [
      `name="${xmlEsc(sv.name)}"`,
      `type="${xmlEsc(sv.type ?? 'number')}"`,
      `initial="${xmlEsc(sv.initial ?? '')}"`,
    ];
    if (sv.unit) attrs.push(`unit="${xmlEsc(sv.unit)}"`);
    if (sv.low !== undefined) attrs.push(`low="${sv.low}"`);
    if (sv.high !== undefined) attrs.push(`high="${sv.high}"`);
    lines.push(`  <statevar ${attrs.join(' ')}/>`);
  }
  if (iface.logic && (iface.logic.nodes.length > 0 || iface.logic.wires.length > 0)) {
    serializeLogic(iface.logic, lines);
  }
  for (const w of iface.widgets) {
    lines.push(
      `  <widget id="${xmlEsc(w.id)}" type="${xmlEsc(w.type)}" ` +
      `x="${w.x}" y="${w.y}" w="${w.w}" h="${w.h}">`
    );
    for (const sig of w.signals) {
      const colorAttr = sig.color ? ` color="${xmlEsc(sig.color)}"` : '';
      lines.push(`    <signal ds="${xmlEsc(sig.ds)}" name="${xmlEsc(sig.name)}"${colorAttr}/>`);
    }
    for (const [key, value] of Object.entries(w.options)) {
      lines.push(`    <option key="${xmlEsc(key)}" value="${xmlEsc(value)}"/>`);
    }
    lines.push(`  </widget>`);
  }
  lines.push(`</interface>`);
  return lines.join('\n');
}

/**
 * Parse an interface XML string into an Interface object.
 *
 * Expected XML format:
 * <interface name="..." version="1">
 *   <widget id="w1" type="textview" x="100" y="50" w="200" h="60">
 *     <signal ds="stub" name="sine_1hz" color="#ff0000"/>
 *     <option key="label" value="Sine 1Hz"/>
 *   </widget>
 * </interface>
 */
/** Parse a <split>/<leaf> element into a PlotLayout node. */
function parseLayout(el: Element): PlotLayout {
  if (el.tagName === 'leaf') {
    return { type: 'leaf', widget: el.getAttribute('widget') ?? '' };
  }
  // split
  const dir = el.getAttribute('dir') === 'v' ? 'v' : 'h';
  const ratio = parseFloat(el.getAttribute('ratio') ?? '0.5');
  const children = Array.from(el.children).filter(
    c => c.tagName === 'split' || c.tagName === 'leaf'
  );
  const a = children[0] ? parseLayout(children[0]) : { type: 'leaf', widget: '' } as PlotLayout;
  const b = children[1] ? parseLayout(children[1]) : { type: 'leaf', widget: '' } as PlotLayout;
  return { type: 'split', dir, ratio: isNaN(ratio) ? 0.5 : ratio, a, b };
}

/** Parse a <logic> element into a LogicGraph (nodes + wires). */
function parseLogic(logicEl: Element): LogicGraph {
  const nodes: LogicGraph['nodes'] = [];
  const wires: LogicGraph['wires'] = [];
  const groups: NonNullable<LogicGraph['groups']> = [];

  for (const el of Array.from(logicEl.children)) {
    if (el.tagName === 'group') {
      const id = el.getAttribute('id') ?? '';
      if (!id) continue;
      const members: string[] = [];
      for (const child of Array.from(el.children)) {
        if (child.tagName !== 'member') continue;
        const mid = child.getAttribute('id');
        if (mid) members.push(mid);
      }
      groups.push({
        id,
        label: el.getAttribute('label') ?? '',
        members,
        collapsed: el.getAttribute('collapsed') === '1',
      });
    } else if (el.tagName === 'node') {
      const id = el.getAttribute('id') ?? '';
      if (!id) continue;
      const params: Record<string, string> = {};
      for (const child of Array.from(el.children)) {
        if (child.tagName !== 'param') continue;
        const key = child.getAttribute('key');
        const value = child.getAttribute('value');
        if (key !== null && value !== null) params[key] = value;
      }
      nodes.push({
        id,
        kind: (el.getAttribute('kind') ?? 'action.write') as LogicGraph['nodes'][number]['kind'],
        x: parseFloat(el.getAttribute('x') ?? '0'),
        y: parseFloat(el.getAttribute('y') ?? '0'),
        params,
      });
    } else if (el.tagName === 'wire') {
      const from = el.getAttribute('from');
      const to = el.getAttribute('to');
      const port = el.getAttribute('port');
      if (from && to) wires.push({ from, to, ...(port ? { fromPort: port } : {}) });
    }
  }

  return { nodes, wires, ...(groups.length ? { groups } : {}) };
}

export function parseInterface(xml: string): Interface {
  const parser = new DOMParser();
  const doc = parser.parseFromString(xml, 'application/xml');

  // Check for parse errors
  const parseError = doc.querySelector('parsererror');
  if (parseError) {
    throw new Error(`XML parse error: ${parseError.textContent}`);
  }

  const root = doc.documentElement;
  if (root.tagName !== 'interface') {
    throw new Error(`Expected root element <interface>, got <${root.tagName}>`);
  }

  const ifaceId = root.getAttribute('id') ?? '';
  const name = root.getAttribute('name') ?? 'Untitled';
  const version = parseInt(root.getAttribute('version') ?? '1', 10);
  const w = parseInt(root.getAttribute('width') ?? '1200', 10);
  const h = parseInt(root.getAttribute('height') ?? '800', 10);
  const kind = root.getAttribute('kind') === 'plot' ? 'plot' : undefined;

  let layout: PlotLayout | undefined;
  if (kind === 'plot') {
    const layoutEl = Array.from(root.children).find(el => el.tagName === 'layout');
    const rootNode = layoutEl
      ? Array.from(layoutEl.children).find(el => el.tagName === 'split' || el.tagName === 'leaf')
      : undefined;
    if (rootNode) layout = parseLayout(rootNode);
  }

  const statevars: StateVar[] = [];
  for (const el of Array.from(root.children)) {
    if (el.tagName !== 'statevar') continue;
    const name = el.getAttribute('name') ?? '';
    if (!name) continue;
    const type = el.getAttribute('type');
    const low = el.getAttribute('low');
    const high = el.getAttribute('high');
    const unit = el.getAttribute('unit');
    statevars.push({
      name,
      type: type === 'bool' || type === 'string' ? type : 'number',
      initial: el.getAttribute('initial') ?? '',
      ...(unit ? { unit } : {}),
      ...(low !== null ? { low: parseFloat(low) } : {}),
      ...(high !== null ? { high: parseFloat(high) } : {}),
    });
  }

  const logicEl = Array.from(root.children).find(el => el.tagName === 'logic');
  const logic = logicEl ? parseLogic(logicEl) : null;

  const widgets: Widget[] = [];

  for (const el of Array.from(root.children)) {
    if (el.tagName !== 'widget') continue;

    const id = el.getAttribute('id') ?? '';
    const type = el.getAttribute('type') ?? 'unknown';
    const x = parseFloat(el.getAttribute('x') ?? '0');
    const y = parseFloat(el.getAttribute('y') ?? '0');
    const w = parseFloat(el.getAttribute('w') ?? '100');
    const h = parseFloat(el.getAttribute('h') ?? '60');

    const signals: Array<SignalRef & { color?: string }> = [];
    const options: Record<string, string> = {};

    for (const child of Array.from(el.children)) {
      if (child.tagName === 'signal') {
        const ds = child.getAttribute('ds') ?? '';
        const sigName = child.getAttribute('name') ?? '';
        const color = child.getAttribute('color') ?? undefined;
        signals.push({ ds, name: sigName, ...(color ? { color } : {}) });
      } else if (child.tagName === 'option') {
        const key = child.getAttribute('key');
        const value = child.getAttribute('value');
        if (key !== null && value !== null) {
          options[key] = value;
        }
      }
    }

    widgets.push({ id, type, x, y, w, h, signals, options });
  }

  return {
    id: ifaceId, name, version, w, h, widgets,
    ...(kind ? { kind } : {}),
    ...(layout ? { layout } : {}),
    ...(statevars.length > 0 ? { statevars } : {}),
    ...(logic && (logic.nodes.length > 0 || logic.wires.length > 0) ? { logic } : {}),
  };
}
